Sensors and tables
In the previous section I described the method used for table interpolation and how it can be used for equations of two variables to obtain quick results. The method presented is based on the use of a ratio for the index, where the ratio is set to 100% = 1024. However, most times, the value of the index does not hold to the same numbering scale. As a result a method needs to be implemented to either be able to use random numbers on the axis, or convert the random numbers to a ratio. Again, reverting to the primary rule, “computers are stupid”, it is easier to do the work in one place than in multiple places. The system already converts an A/D value into its representative sensor reading, so converting the sensor value to a ratio should be a similar operation just inverted.
Converting to a ratio not only has the tabular form of inverting the linear table from earlier. Two simpler forms are also obvious; a ratio between two values, and a ratio between zero and a maximum value. The table structure still holds, and would be an additional calibration, so this process will be added to the table operator. The inversion operation would resolve a passed value into a ratio. The method will be to locate the bounding values in the table, and determine the percentage remaining. As before the equation dy/dx = y-y0/x-x0, but this time solving for x instead of y
(Y1−Y0)(X1−X0) =(num−Y0)(val−X0) => val=((num−Y0)(X1−X0))(Y1−Y0)
The process requires an additional step of locating the boundary values Y1 and Y0 from the table, because the process is inverting the simple conversion. It becomes necessary to scan the values in the table for a pair of values where one value is less than the input number and the other is greater than the number. The array index values provide the values for X0 and X1. The value of dx is based on the number of elements in the array. Because the process uses another calibration table, it is added to the get table group, in tables.c, as an additional type.
case 3: if (y < calTbl[n].yLen) { off += y * calTbl[n].xLen; /************************************************************************************** * Three types of index ranges. * 1x1 - 0 to Max value (max is stored) * 2x1 - Min, Max value (both stored) * Nx1 - linear interpolate best fit **************************************************************************************/ yI = ONE / (calTbl[n].xLen > 2 ? calTbl[n].xLen : 1); z[0] = calTbl[n].xLen < 2 ? 0 : getCal(off); z[1] = calTbl[n].xLen < 2 ? getCal(off) : getCal(off + 1); if ((x > z[0]) ^ (z[1] > z[0])) { rVal = -1; } else { for (xI = 2, z[2] = 0; (xI < calTbl[n].xLen) && ((x > z[1]) ^ (z[0] > z[1])); xI++, z[2] += yI) { z[0] = z[1]; z[1] = getCal(off + xI); } if ((x > z[1]) ^ (z[0] > z[1])) { z[0] = z[1]; z[1] = z[0] + z[0] - getCal(off + xI - 2); z[2] += yI; } if ((x > z[1]) ^ (z[0] > z[1])) { rVal = ONE; } else { rVal = z[2] + (SWord)(((SLong)yI * (SLong)(x - z[0])) / (SLong)(z[1] - z[0])); } } } break;
Why am I building this table?
Now that the tool is ready, it is possible to build our first table. But first, I get to talk a little bit about the operation of the standard internal combustion engine. Some will glaze over at this point, while others can’t wait for me to continue. For controlled combustion to occur in a normal internal combustion engine, three things are required; air, fuel, and spark. Removing one or more of these causes the motor to not run, and by adjusting the quantities and timing can make the motor run slower or faster, and more or less efficiently. For a standard four cycle motor, The cylinder rises and falls twice, with the valves opening to move exhaust, air, and fuel through the system, and an ignition spark. The timing of the spark is critical to the proper operation of the system. The spark provides an ignition of a chemical reaction producing heat and energy which expands the gasses in the contained area. The resulting increased pressure pushes the cylinder which drives the crankshaft rotation, this action is called the power cycle (first of four), the second cycle is a result of the effect of rotation causing the cylinder to rise again on the second half of the crank shaft rotation. During this cycle the exhaust port valve(s) opens letting the spent reacted gasses be pushed out the exhaust manifold, this action is called the exhaust cycle. The system is coasting on the energy provided by the power cycle, and the cylinder goes back down. At this time the exhaust port valve(s) close, and the intake valve(s) open letting in air that is mixed with atomized fuel in suspension, this is the third cycle, called the fueling cycle. At the bottom of the cycle, as the cylinder begins its rise again, the intake valve(s) close again sealing the chamber causing the fuel and air mixture to become compressed as the cylinder moves to the top of the cycle, this is called the compression cycle. At the proper moment of the compression cycle, the spark ignites the fuel, and the process starts over again.
Factors that affect the behavior of the process are:
Atomization of the fuel – The smaller the droplets the more surface area is available for reaction to the air.
Volume of air – The more air, or the ease of movement of air, means less work is needed to get the air into the cylinder.
Timing of the spark – This is the fun explanation, so I am going to go into more detail.
Using a super slow motion camera to record the spark event and power cycle of a cylinder, it shows when the spark ignites the fuel, it does not all happen immediately. In fact it begins at the point of ignition (the spark) and moves outward in a plume, getting bigger as more of the fuel reacts with the air in the chamber. The time it takes the plume to reach the top of the cylinder head affects the energy that is transferred. Also, where the cylinder is in its cycle (rising or falling). A good point of reference is when the edge of the plume reaches the top of the cylinder, and to synchronize this with the cylinder reaching it topmost point of the cycle (called top dead center or TDC). Because of the nature of cylinder chambers, and for safety, there is a gap between the cylinder head at TDC and the upper surface of the chamber. Because the plume requires time to cover this distance, the spark event is triggered before TDC. This is called the spark advance.
Changes in the spark advance affect the amount of power transferred to the crankshaft. Slightly earlier, and the pressure at the TDC point is increased and the motor runs faster. Too much earlier, and there is a negative effect causing pressure in the wrong direction, potentially damaging the system. Slightly later, and the pressure has less effect and the motor runs slower. Too much later, and the exhaust gasses might still be combusting as they exit the exhaust manifold, potentially causing damage to the system.
The other parameter that can affect timing advance is air pressure. The higher the level of air pressure, the more air there is to react with the fuel. The lower the pressure, the less air to react. If there is less air for reaction, the reaction will take longer, so the plume moves slower, and the spark needs to occur earlier. The higher the pressure, the more air to react, plus, higher compression, so the spark can occur later. Which means the system needs to combine engine speed (RPM) and air pressure (manifold pressure or MAP), to produce a proper advance value. The question is, what units should be used for advance? The common accepted value is degrees, where positive degrees is the advance, and relates to the angle before TDC. Retard is any movement of the event in the direction opposite of advance.
Something to note here, because fuel quantity and pressure together affect the power output of the reaction, it is possible to make the engine turn faster just by adding more fuel. Luckily, for now, fuel quantity is not a part of this control system. That will be waiting for a future installment.
With that aside, now to focus on constructing a table. The table will be a base advance table, yielding an angle of advance based on values for engine speed (RPM) and manifold pressure (MAP). Tables are defined to use ratios but the values of MAP and RPM are absolute numbers, and need to be converted back to a ratio using the newly provided process. RPM is a single ended value with a maximum value, while MAP generally is either the scale of the full range, or a portion of the absolute range based on logical use case. MAP is necessary for system operation, because RPM is affected by advance which in turn affects the value of advance. Without the control of air pressure changes, the motor would remain unchanged at a constant speed, or stall, or have a runaway acceleration. MAP is controlled by the air intake vein, which is, in turn, controlled by the throttle.
Back to the first sensor
Setting up the ranges MAP will range from 107.00 kPa to 13.50 kPa, and RPM will range from 0 to 7000. Using the ratio processing, two tables will be created; a map index (8x1) with 8 evenly spaced values from 107 to 13.5, and a maximum RPM of 7000. The values from getRpm(), and getMap(), developed earlier, will be used for the inputs. The angle values will need to be converted from common degrees to binary radians described previously (360 degrees = 32768). What values to use for advance? Usually a good starting point is in the range of 20 degrees. With the numbers increasing as pressure goes up, and increasing as RPM goes up.
How to make a table
Because I have hinted that this needs to be done, here is the process for adding tables to the current system.
Step 1: add the table control structure to the table array in tables.c. Each entry has 4 fields, first is the array type, 0=discrete, 1=linear, 2=two dimensioned, and now 3=index. Second field is the table width or x component of the array, and so, the third is the height or y component of the array. The fourth field is an offset into memory where the table begins constructed as an array of 16bit values.
Step 2: add a value for the offset into memory to the list of #define items at the top of tables.c
Step 3: in tables.h add an enumeration for the table for use by getTable functions.
Step 4: make a call to get the particular value from the table that calls getTableVal and passes the enumerated value and the x and y component values.
I really hate the word “just” in technical descriptions, so if you follow these steps, you should have a workable table. The table is also accessible with the commands created earlier for populating the calibration values. Here are the changes to the existing code (the 2 1x1 tables for the tooth count and flags from the previous installment is also added here):
In tables.c (this is really step 2 of the 4 steps)
#define ENG_FLAGS (A2D_CONVERT+64) // a 1x1 table of flags for rising and falling edge #define ENG_TEETH (ENG_FLAGS+1) // a 1x1 table with number of teeth #define ENG_MAX_RPM (ENG_TEETH+1) // a 1x1 index table for rpm ratio #define ENG_BASE_ADV (ENG_MAX_RPM+1) // an 8x8 2d table for base advance values #define MAX_CAL (ENG_BASE_ADV+64) // marks the end of memory used
Also in tables.c added to calTbl[] (this is step 1)
,{ 0,1,1,ENG_FLAGS } // engine flags ,{ 0,1,1,ENG_TEETH } // tooth count ,{ 3,1,1,ENG_MAX_RPM } // max RPM for getting ratio ,{ 2,8,8,ENG_BASE_ADV } // 8x8 base advance
Update to tables.h enumeration (step 3)
,tEngFlgs ,tEngTeth ,tEngMaxRpm ,tEngBaseAdv ,MAX_TBL
Shortcut used here
Because the A/D counts is already a 10 bit number the sensor conversion is used only for display purposes, and the raw A/D is used for table index. Remember, the computer doesn’t care about units only bits. The lookups for the two sensors and the RPM should also be added to the monitors in addition to the base advance.
SWord getRpmIdx(void) { return getTableVal(tEngMaxRpm, getRpm(), 0); } SWord getEctIdx(void) { return getA2D(0); } SWord getMapIdx(void) { return getA2D(1); } SWord getEct(void) { return getSns(0); } SWord getMap(void) { return getSns(1); } SWord getBaseAdv(void) { return getTableVal(tEngBaseAdv, getRpmIdx(), getMapIdx()); }
The entries for everything is already available in the monitors, except RPM, RPM index, and base advance, so add the entries to the monitor table in monitors.c
static const monCall mon[] = { A2d0,A2d1,A2d2,A2d3,A2d4,A2d5,A2d6,A2d7 ,Sns0,Sns1,Sns2,Sns3,Sns4,Sns5,Sns6,Sns7 ,getRpm, getRpmIdx ,getBaseAdv };
More to come. The system is capable of determining RPM, and to provide a base advance, next I will solve the problem of identifying where in the ca