element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Ben Heck Featured Content
  • Challenges & Projects
  • element14 presents
  • element14's The Ben Heck Show
  • Ben Heck Featured Content
  • More
  • Cancel
Ben Heck Featured Content
Forum More Engine Management
  • Blog
  • Forum
  • Documents
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Ben Heck Featured Content to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Forum Thread Details
  • Replies 6 replies
  • Subscribers 39 subscribers
  • Views 866 views
  • Users 0 members are here
  • gasoline_engine_management
  • arduino
Related

More Engine Management

jack.chaney56
jack.chaney56 over 6 years ago

In the previous discussion, I presented a base system for engine management. The problem with what was created is, it only runs in steady state. Steady state for an engine is constant rpm and load, no acceleration, and no change to demand. The software components of the system have been intentionally designed to be independent of hardware processor. The specific platform is the Arduino Nano and the 328p processor. The choice was as an exercise to see if it could be done, and to force a level of discipline for the programming model. The other item of note from the previous discussion is, I am a software person primarily, with a working knowledge of electronic hardware. I am happy to admit to shortcomings in any circuits that I present, and encourage correction.

 

This second section will deal with making improvements to the basic software to manage the asynchronous events. Some items will make reference to the previous discussion, so please review for a full description. If something was left out, I am happy to fill in the detail. Jumping in to the first item which is a one time event. The engine start. When the engine is cranking, before ignition timing is captured, fuel is being input, and not burned. To correct the state, once the engine starts, the acceleration is raised temporarily to "burn off" the excess fuel. This is done by adjusting the advance, first increasing, then reducing back to steady state.  Also, during crank (before ignition capture), the advance is reduced to almost zero. Because the advance goes from near zero to a large angle, a measured transition is needed. This is called the ramp in rate. Also, the recovery from the large angle to the steady state angle, is again a measured value. This is called the decay. The startup sequence then becomes a small state operation. Crank > Ramp to max > Decay to steady > Steady State. In each of the states a value is combined as part of the total advance.

 

The ramp in and decay are governed by the RPM of the motor, so a counter tied to the cam signal is implemented. The four states are monitored with a simple switch operation. The values for ramp in and decay are defined in calibration, as well as the values for max and minimum advance. The other part is to implement a counter of some sort that is updated during the cam signal detect (same place as setting cam detected flag). The sequence works sort of like this:

  • Engine off - Retrieve from calibration values for min, max, ramp, and decay.
  • Crank to start - Set advance to minimum until ignition is detected, also set counter to ramp in value when ignition is detected change state.
  • Ramp in - Adjust advance from minimum to maximum with a slope = counter / ramp until max is reached then set counter to decay and change state
  • Decay out - Adjust advance from max to steady state with a slope = counter / decay until steady state is reached then change state
  • Steady State - watch for engine stop

 

SWord updateStartupTrim(SWord t) {
 static SWord ramp;
 static SWord min = 0;
 static SWord max = 0;
 static UByte stState = 0;
 switch(stState) {
 case 0:  // engine off, crank to start
  min = getIgnStartupAdjustment() - t; ramp = getIgnStartupRampIn(); if (ramp == 0) { ramp = 1; }
  ignStTrim = getRpm() > 0 ? min : 0;
  if (getRpm() > getIgnRpmMinRun()) { camTrimCt = ramp; stState = 1; }
  break;
 case 1:  // ramp in
  max = getIgnStartupTrim();
  if (camTrimCt > 0) { ignStTrim = max - (SWord)((SLong)camTrimCt * (SLong)(max - min) / (SLong)ramp); }
  else { ignStTrim = max; ramp = getIgnStartupDecay(); if (ramp == 0) { ramp = 1; } camTrimCt = ramp; stState = 2; }
  break;
 case 2:  // decay
  if (camTrimCt > 0) { ignStTrim = (SWord)((SLong)max * (SLong)camTrimCt / (SLong)ramp); }
  else { ignStTrim = 0; stState = 3; }
  break;
 default: // running
  ignStTrim = 0;
  if (isEngStopped()) { stState = 0; }
  break;
 }
 return ignStTrim;
}

 

The variable camTrimCt is the counter which gets decremented on each cam.

 

Good news is, there are lots of exception and asynchronous activities to manage, so there will be lots offered here

 

Jack

  • Sign in to reply
  • Cancel

Top Replies

  • jack.chaney56
    jack.chaney56 over 6 years ago +2
    Here is where I get some people upset. I generally have great respect for electrical engineers, and their capabilities to develop really good hardware solutions. I am by training a software development…
  • jack.chaney56
    jack.chaney56 over 6 years ago +1
    Should add the link to the previous discussion as well. Engine Management
Parents
  • jack.chaney56
    jack.chaney56 over 6 years ago

    Less is more.

     

    Wow, talking about linear interpolation between points on a curve, takes me back to my Calculus days. ...and at the time, I thought it would never have a practical application. Using a table of values and performing a linear interpolation between table elements is a very good and fast way to perform sensor conversions.  For engine management, it is often necessary to use two variables and create a two dimensional table.  Most often, the variables are pressure and rpm, but as before, do not perform conversions if they are not necessary. In the previous discussion, I presented the function for doing planar interpolation.  If the interpolation is done, using manifold pressure and rpm, a good value can be obtained for base advance. Because the table is in EEPROM space, it is possible through experimentation, or trial and error, to obtain calibration values that provide optimal performance.

     

    From the calculus days, it is noted, the more points along the curve, the better the approximation. The same is also true for calibration tables. a 16x16 table offers better control than a 4x4 table. The problem is, in a small system, memory is a limited resource, so size of the table needs to be regulated.  Again, speed is the key when working out the algorithm for table interpolation. The other feature of the code is to make it, so the same function would work for any type of table, and any size of table. To do this, a secondary array is created containing the table type (0=discrete, 1=linear, 2=planar) where discrete is accessed by individual cell, linear is a 1 dimensioned linear interpolation, and planar is a two dimensioned array. To reduce complexity, all tables will be maintained as two dimensioned arrays. and the retrieval of data is based on type. Type 0 returns the value of the cell at array coordinates (x,y). Type 1 returns the linear interpolated value of a row (y), and Type 2 returns the planar interpolated value.

     

    The other effort was to provide portability and scalability to the code, so a level of decoupling is implemented. The fetching of the cell is done as a function call. The function can create any amount of detachment including one table existing in internal memory, while another references a value from a remote data store. This would be an extreme case, but the goal is to be fully scalable. The implementation here only uses EEPROM for calibration storage, all the variables are 16 bit integer, and the interface record only has table type, width, height, and eeprom offset.  In an ideal world, using real objects, the elements would more likely be; table type, width, height, table label, and data type. It is not too difficult for a skilled programmer to provide these extensions an a more complex system.

     

    The implementation for my solution is:

     

    /* Tables structure */
    struct tblTbl {
        UByte type;         /* 0 = discrete, 1 = linear, 2 planar, 3 = index */
        UByte xLen;         /* x size - width of table */
        UByte yLen;         /* y size - height of table (linear is numbered row) */
        UWord offs;         /* memory location offset of the table */
    };
    /* n is the enumerated table to access */
    /* x and y are the cell coordinates of the virtual array */
    SWord getTableCel(UWord n, UByte x, UByte y) {
        SWord rVal = 0;
        if ((n < MAX_TBL) && (x < calTbl[n].xLen) && (y < calTbl[n].yLen)) {
            rVal = getCal(calTbl[n].offs + (y * calTbl[n].xLen) + x);
        }
        return rVal;
    }
    /* n is the enumerated table to access */
    /* x and y are the cell coordinates of the virtual array */
    /* d is the data item to insert into the table */
    void putTableCel(UWord n, UByte x, UByte y, SWord d) {
        if ((n < MAX_TBL) && (x < calTbl[n].xLen) && (y < calTbl[n].yLen)) {
            putCal(calTbl[n].offs + (y * calTbl[n].xLen) + x, d);
        }
    }
    /**************************************************************************************************
     Tables are all NxN configuration of 16 bit values, and are accessed by 4 different methods.
     The first is discrete that just returns the value at the cell (x,y).
     The second is a linear interpolation with an x-index of 0-1023, and divided into n equal
       sections. The y index is for multiple instances of the row. An extra phantom column is
       added to the end for a right boundary.
     The third is a planar interpolation with indexes of 0-1023, and divided into NxM equal
       sections. An extra phantom row & column are added to the outer for a boundary.
     The last type is an inverse of the second type performing a best fit from a provided value
       and returning index of 0-1023, 0 if the provided value is less than the lowest, and 1023
       if greater than the highest.
    **************************************************************************************************/
    SWord getTableVal(UWord n, SWord x, SWord y) {
        SWord z[4], rVal = 0;
        SWord dX, dY, xI, yI;
        ULong off;
        SLong base, K[4];
        if (n < MAX_TBL) { off = calTbl[n].offs;
            switch (calTbl[n].type) {
            case 1: /* Linear */
                y = y < calTbl[n].yLen ? y : (calTbl[n].yLen - 1);
                dX = ONE / calTbl[n].xLen; xI = x % dX; x /= dX; if (x >= calTbl[n].xLen) { xI = dX; x = calTbl[n].xLen - 1; }
                z[0] = getTableCel(n, x, y);
                z[1] = (x + 1) < calTbl[n].xLen ? getTableCel(n, x+1, y) : (z[0] + z[0] - getTableCel(n, x-1, y));
                base = ((SLong)z[1] - (SLong)z[0]) * (SLong)xI / (SLong)dX;
                rVal = z[0] + (SWord)base;
                break;
            case 2: /* Planar */
                dY = ONE / calTbl[n].yLen; yI = y % dY; y /= dY; if (y >= calTbl[n].yLen) { yI = dY; y = calTbl[n].yLen - 1; }
                dX = ONE / calTbl[n].xLen; xI = x % dX; x /= dX; if (x >= calTbl[n].xLen) { xI = dX; x = calTbl[n].xLen - 1; }
                z[0] = getTableCel(n, x, y);
                z[1] = (x + 1) < calTbl[n].xLen ? getTableCel(n, x+1, y) : (z[0] + z[0] - getTableCel(n, x-1, y));
                z[2] = (y + 1) < calTbl[n].yLen ? getTableCel(n, x, y+1) : (z[0] + z[0] - getTableCel(n, x, y-1));
                z[3] = ((y + 1) < calTbl[n].yLen) && ((x + 1) < calTbl[n].xLen) ? getTableCel(n, x+1, y+1) : (z[2] + z[0] - z[1]);
                /**************************************************************************************
                * The equation for a planar surface interpolation for NxM points is
                * Z0(x2-X)(y2-Y) + Z1(X-x1)(y2-Y) + Z2(x2-X)(Y-y1) + Z3(X-x1)(Y-y1)
                * -----------------------------------------------------------------
                *                          (x2-x1)(y2-y1)
                * X and Y are offsets. Z are the value of the corners
                **************************************************************************************/
                K[0] = (SLong)(dX - xI) * (SLong)(dY - yI) * (SLong)z[0];
                K[1] = (SLong)xI        * (SLong)(dY - yI) * (SLong)z[1];
                K[2] = (SLong)(dX - xI) * (SLong)yI        * (SLong)z[2];
                K[3] = (SLong)xI        * (SLong)yI        * (SLong)z[3];
                base = (SLong)dX * (SLong)dY;
                rVal = (SWord)((K[0] + K[1] + K[2] + K[3]) / base);
                break;
            default: /* Discrete */
                rVal = getTableCel(n, x, y);
                break;
            }
        }
        return rVal;
    }

     

    by observation, note, the scalar is based on 1024, which is why I use it as a reference ratio.

     

    A last conversion is necessary, because RPM is linear and not limited from 0 to 1023, a ratio value is calculated using a minimum and maximum RPM. The values of min and max rpm can be saved as discrete calibration values, and calculated at runtime.

     

    Fun part of this, so far the source code provided (with the exception of the cam and crank interrupt stuff from the previous), is fully adaptable to nearly any application. Engine management is just one application.

     

    Jack

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
Reply
  • jack.chaney56
    jack.chaney56 over 6 years ago

    Less is more.

     

    Wow, talking about linear interpolation between points on a curve, takes me back to my Calculus days. ...and at the time, I thought it would never have a practical application. Using a table of values and performing a linear interpolation between table elements is a very good and fast way to perform sensor conversions.  For engine management, it is often necessary to use two variables and create a two dimensional table.  Most often, the variables are pressure and rpm, but as before, do not perform conversions if they are not necessary. In the previous discussion, I presented the function for doing planar interpolation.  If the interpolation is done, using manifold pressure and rpm, a good value can be obtained for base advance. Because the table is in EEPROM space, it is possible through experimentation, or trial and error, to obtain calibration values that provide optimal performance.

     

    From the calculus days, it is noted, the more points along the curve, the better the approximation. The same is also true for calibration tables. a 16x16 table offers better control than a 4x4 table. The problem is, in a small system, memory is a limited resource, so size of the table needs to be regulated.  Again, speed is the key when working out the algorithm for table interpolation. The other feature of the code is to make it, so the same function would work for any type of table, and any size of table. To do this, a secondary array is created containing the table type (0=discrete, 1=linear, 2=planar) where discrete is accessed by individual cell, linear is a 1 dimensioned linear interpolation, and planar is a two dimensioned array. To reduce complexity, all tables will be maintained as two dimensioned arrays. and the retrieval of data is based on type. Type 0 returns the value of the cell at array coordinates (x,y). Type 1 returns the linear interpolated value of a row (y), and Type 2 returns the planar interpolated value.

     

    The other effort was to provide portability and scalability to the code, so a level of decoupling is implemented. The fetching of the cell is done as a function call. The function can create any amount of detachment including one table existing in internal memory, while another references a value from a remote data store. This would be an extreme case, but the goal is to be fully scalable. The implementation here only uses EEPROM for calibration storage, all the variables are 16 bit integer, and the interface record only has table type, width, height, and eeprom offset.  In an ideal world, using real objects, the elements would more likely be; table type, width, height, table label, and data type. It is not too difficult for a skilled programmer to provide these extensions an a more complex system.

     

    The implementation for my solution is:

     

    /* Tables structure */
    struct tblTbl {
        UByte type;         /* 0 = discrete, 1 = linear, 2 planar, 3 = index */
        UByte xLen;         /* x size - width of table */
        UByte yLen;         /* y size - height of table (linear is numbered row) */
        UWord offs;         /* memory location offset of the table */
    };
    /* n is the enumerated table to access */
    /* x and y are the cell coordinates of the virtual array */
    SWord getTableCel(UWord n, UByte x, UByte y) {
        SWord rVal = 0;
        if ((n < MAX_TBL) && (x < calTbl[n].xLen) && (y < calTbl[n].yLen)) {
            rVal = getCal(calTbl[n].offs + (y * calTbl[n].xLen) + x);
        }
        return rVal;
    }
    /* n is the enumerated table to access */
    /* x and y are the cell coordinates of the virtual array */
    /* d is the data item to insert into the table */
    void putTableCel(UWord n, UByte x, UByte y, SWord d) {
        if ((n < MAX_TBL) && (x < calTbl[n].xLen) && (y < calTbl[n].yLen)) {
            putCal(calTbl[n].offs + (y * calTbl[n].xLen) + x, d);
        }
    }
    /**************************************************************************************************
     Tables are all NxN configuration of 16 bit values, and are accessed by 4 different methods.
     The first is discrete that just returns the value at the cell (x,y).
     The second is a linear interpolation with an x-index of 0-1023, and divided into n equal
       sections. The y index is for multiple instances of the row. An extra phantom column is
       added to the end for a right boundary.
     The third is a planar interpolation with indexes of 0-1023, and divided into NxM equal
       sections. An extra phantom row & column are added to the outer for a boundary.
     The last type is an inverse of the second type performing a best fit from a provided value
       and returning index of 0-1023, 0 if the provided value is less than the lowest, and 1023
       if greater than the highest.
    **************************************************************************************************/
    SWord getTableVal(UWord n, SWord x, SWord y) {
        SWord z[4], rVal = 0;
        SWord dX, dY, xI, yI;
        ULong off;
        SLong base, K[4];
        if (n < MAX_TBL) { off = calTbl[n].offs;
            switch (calTbl[n].type) {
            case 1: /* Linear */
                y = y < calTbl[n].yLen ? y : (calTbl[n].yLen - 1);
                dX = ONE / calTbl[n].xLen; xI = x % dX; x /= dX; if (x >= calTbl[n].xLen) { xI = dX; x = calTbl[n].xLen - 1; }
                z[0] = getTableCel(n, x, y);
                z[1] = (x + 1) < calTbl[n].xLen ? getTableCel(n, x+1, y) : (z[0] + z[0] - getTableCel(n, x-1, y));
                base = ((SLong)z[1] - (SLong)z[0]) * (SLong)xI / (SLong)dX;
                rVal = z[0] + (SWord)base;
                break;
            case 2: /* Planar */
                dY = ONE / calTbl[n].yLen; yI = y % dY; y /= dY; if (y >= calTbl[n].yLen) { yI = dY; y = calTbl[n].yLen - 1; }
                dX = ONE / calTbl[n].xLen; xI = x % dX; x /= dX; if (x >= calTbl[n].xLen) { xI = dX; x = calTbl[n].xLen - 1; }
                z[0] = getTableCel(n, x, y);
                z[1] = (x + 1) < calTbl[n].xLen ? getTableCel(n, x+1, y) : (z[0] + z[0] - getTableCel(n, x-1, y));
                z[2] = (y + 1) < calTbl[n].yLen ? getTableCel(n, x, y+1) : (z[0] + z[0] - getTableCel(n, x, y-1));
                z[3] = ((y + 1) < calTbl[n].yLen) && ((x + 1) < calTbl[n].xLen) ? getTableCel(n, x+1, y+1) : (z[2] + z[0] - z[1]);
                /**************************************************************************************
                * The equation for a planar surface interpolation for NxM points is
                * Z0(x2-X)(y2-Y) + Z1(X-x1)(y2-Y) + Z2(x2-X)(Y-y1) + Z3(X-x1)(Y-y1)
                * -----------------------------------------------------------------
                *                          (x2-x1)(y2-y1)
                * X and Y are offsets. Z are the value of the corners
                **************************************************************************************/
                K[0] = (SLong)(dX - xI) * (SLong)(dY - yI) * (SLong)z[0];
                K[1] = (SLong)xI        * (SLong)(dY - yI) * (SLong)z[1];
                K[2] = (SLong)(dX - xI) * (SLong)yI        * (SLong)z[2];
                K[3] = (SLong)xI        * (SLong)yI        * (SLong)z[3];
                base = (SLong)dX * (SLong)dY;
                rVal = (SWord)((K[0] + K[1] + K[2] + K[3]) / base);
                break;
            default: /* Discrete */
                rVal = getTableCel(n, x, y);
                break;
            }
        }
        return rVal;
    }

     

    by observation, note, the scalar is based on 1024, which is why I use it as a reference ratio.

     

    A last conversion is necessary, because RPM is linear and not limited from 0 to 1023, a ratio value is calculated using a minimum and maximum RPM. The values of min and max rpm can be saved as discrete calibration values, and calculated at runtime.

     

    Fun part of this, so far the source code provided (with the exception of the cam and crank interrupt stuff from the previous), is fully adaptable to nearly any application. Engine management is just one application.

     

    Jack

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube