Time for time.
I discussed cam and crank, and how they give the system location and speed information. I discussed advance and dwell, and why they are necessary for making the engine run properly. Now the next step in the process is to provide the time based operation in a scalable manner, so a simple processor will be able to manage without being overburdened. The work to this point has been focused on a single cylinder, but most engines (at least for motor vehicles) contain more than one cylinder. Also our system only understands steady state, (no variation), so the timing remains static. There has also been an absence of feedback for the system. No user interface, no monitoring, and no inputs. The only input the system has are the cam and crank signals.
Inputs for engine management are usually from sensors. The more expensive the system, the more sensors are available. To start off slowly, the two primary sensors that get used the most, are manifold air pressure (MAP), and throttle position sensor (TPS). It can be argued that if you have one, you can do without the other. This argument will become apparent as we move on. The next sensors that are of value are engine coolant temperature (ECT), and intake air temperature (IAT). These provide a means of adjusting operations based on current temperature and cooling actions. The next level of sensors are used to provide added information for operation in less than standard conditions; the barometric sensor (BARO), and battery voltage level (Vbatt).
Getting the information from the sensors is done through the use of an A/D converter. Many of the new processors have them built in, so they need to be serviced. How an A/D works is a voltage is applied to the input line, and a trigger is activated that charges a capacitor. Then the voltage is removed and a very high speed counter runs to see how long it takes for the voltage level to drop below a particular threshold. Over a particular range, this behaves in a linear manner, and so, an accurate reading of the voltage is possible by converting the number of counts. The more counter bits, the more accurate, and the more expensive. Also the more time it takes for the value to be obtained. The A/D circuit is usually switched through a multiplexor, where a single A/D can manage (or take the values of) many input signals. This is done by setting the MUX value to the correct channel, then performing the A/D process.
A/D converters often have signaling to let the process know the conversion is complete and the value is ready to use. This signal is often a status bit in a register, but also often is provided as an interrupt signal. Both mechanisms have their advantages, and disadvantages. Whatever the disadvantages, I prefer using the interrupt method, and will cover how to do that in a while.
The other component, also often included in the processor, are timer / counters. Most timer components offer modes of operation; continuous, countdown, compare, and capture. The other activity provide by timer sections is the ability to generate interrupts, either periodically, or at a scheduled time. The other resource a timer provides is a link to real time. A computer process does not particularly care how long an operation will take, so it takes as long as it needs. The real world is based on events which happen at predictable times. As a result, a timer can provide a stimulus to the processor to make sure it is performing the right task at the right time. This is done through the use of interrupt messaging, where the interrupt process sets a flag, and the mainline routine monitors the flag for when it is activated.
I have already discussed how cam and crank signals operate through the use of an interrupt and we use the timer to record the moment of the interrupt. The importance of interrupts is evident at this point, so familiarity with their operation is extremely important in order to provide proper management of resources.
Time to start putting together some real applications, by putting some values to the systems discussed. Starting with a static, steady state, operating motor, running at 750RPM (a generally good idle speed), and with a 4 cylinder configuration, a 2x (two crank events per rotation) timing wheel, and a single pulse cam sensor set to just before TDC1 (cam to TDC = 0). The timer is set to free running at a rate of 1MHz (or 1uS per tic). If everything is operating properly, the system will be triggering a crank interrupt every 0.04 seconds. Or 40000 tics of our counter. Next step is deciding what needs to happen when. The interrupt from the crank will trigger a process that captures the value of the free running timer and stores it in a variable curTime (for current time). Next calculate the value of curDiff (for current difference), for this the value of the preTime (for previous time) is subtracted from curTime. Before exiting the interrupt routine, the value of preTime is replaced by curTime. As I stated before, at first the system will deal with ideal situations, then address exceptions. Here is the first exception, what happens when the counter rolls over a maximum value, and the preTime is greater than the curTIme value. The simple way is to check before the operation and add in the maxTime (for maximum time value). If curTime < preTime, then curDiff = (maxTime + curTime) – preTime. Or a little cleaner curDiff = maxTime – (preTime – curTime). For this system, I am also keeping to integer operations to prevent introducing conversion error, and because integer operations are run in registers, attempts will be made to prevent overflow conditions.
This takes the discussion to a delicate area of how accurate and how big are the numbers that are being used. In the example, 40000 was needed to hold the value of curDiff, when the motor is running at 750RPM (idle speed). Working out some constants real quickly and adding our tooth count as TEETH;
TICS_PER_mS = 1000
mS_PER_SEC = 1000
SEC_PER_MIN = 60
TICS_PER_MIN = (SEC_PER_MIN*mS_PER_SEC*TICS_PER_MIN)
RPM = TICS_PER_MIN / (curDiff * TEETH)
By doing the inverse of the formula, we can see as RPM decreases, the value of curDiff increases, so limits are now able to be established. If the free running timer is a 16 bit value, the lower RPM limit is reached when curDiff = 65535. For the 2x crank, that becomes, 60000000 / (65535 * 2), or slightly less than 458 RPM. The problem with this value is, capture of run to start usually happens at a slower rate (200-300 RPM). Using a larger integer provides two improvements, first, the 60000000 value doesn’t need special handling, and second with 32 bits the system can read extremely low crank speed. The system now knows how fast, and only needed a simple subtraction in the interrupt. But it is also necessary to know position.
Each crank event provides positional information, however, the position is still obscure without the proper reference. Remembering our system operates in steady state, the position is known, because it is one tooth further in the revolution from the previous event. When does the resolution of which tooth we are on get determined? This operation happens continuously, on every occurrence of a cam event, and the current tooth is always measured as number of teeth after cam event with the first tooth after cam equaling zero. The resulting positional information is now 0 degrees is at the tooth where CAM_TO_TDC – TOOTH_AFTER_CAM is equal to 0. This provides our additional information for our crank interrupt. On each crank event the value of toothAfterCam is incremented. If the cam event is detected, the value of toothAfterCam is set to 0. Complex cam event detection is reserved for a future installment for now accept the system provides a single pulse for cam, and the interrupt routine only sets a simple event flag. The flag is cleared in the crank operation when the counter is set to zero.
Now on to determining when we need our events to occur. For each cylinder, there are two significant events, first is start of dwell, the second is the ignition of the spark. The calculations are the same for each instance, spEv[n] (for spark event time) is at its TDC[n] angle, less advTotal (for the total calculated advance), and dwEv[n] (for start of dwell) is the angle spEv[n], less dwlAngle (for the total dwell angle).
The events are scheduled based on current value of tooth by calculating the angle at the tooth events. If the event needs to happen between teeth 2 and 3, then the event is added to the queue when tooth 1 is detected. Likewise, if an event is supposed to happen between teeth 30 and 31, then the event is added to the queue when tooth 29 is detected. The other element to keep in mind, from a resource standpoint, there will not be a situation where more than one spark will be occurring at the same time, and by extension, if all the dwells are equal, no two dwells will start at the same time. The result is a simple scheduler using only two timer compares, one for dwell the other for spark.
Lots covered, more to come. Should start on some exceptions next time and how to use the sensor information,
Jack