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

    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 specialist. I have also had a good amount of electrical experience with circuit design.  My experience has shown me, my electrical ability is equivalent to an electrical engineer's programming ability. I am able to hash and bash my way to a hardware solution, but it lacks a level of elegance obtained by expertise.  Likewise, software from most of the electrical engineers that I have experienced, has shown many areas were elegance is a little lacking.

     

    Likewise, there have been a good number of embedded programmers I have worked with, who disprove my observation. These people have learned the lesson, "if the only tool you have is a hammer, all your problems look like nails". Electrical engineers, learn how to make state machines, and they learn how to convert sensor values from a voltage reading. Most cases, they also hold to the idea that floating point numbers solve many of the problems of accuracy.

     

    A course that was taught long ago in the early days of computers, was compiler design. I am not quite sure when it was determined that this discipline was no longer necessary, but I contend it needs to be brought back. The reason is, it provided a good means of showing what instructions did, at a low enough level, to indicate where bottlenecks would occur, and where accuracy is lost. This is where I pull out my soap box and expound the virtues of pure integer math. My premise is, for all except a very few extreme cases, floating point is unnecessary, and for easily greater than half the cases (especially for control systems) more than 16 bits is a luxury.

     

    I like to use the example of a golf course.  On a par 5 hole, the distance from the tee box, to the hole is generally around 500 yards. 500 yards is 1500 feet, which is 18000 inches. Using a 16 bit unsigned number to measure distance, the measurement is a little more than 1/3 of an inch. So it is possible to map a 500 yard field down to 1/3 of an inch using only 16 bit integers. Where elegance is incorporated... remember to always look at the problem from multiple viewpoints.

     

    With the golf problem, the distance to the hole, is actually irrelevant. The important factor is how far can the golfer actually hit the ball. If you were writing a video game to simulate golfing, The behavior is actually to hit the ball, then walk (or ride) to where the ball landed. Simulation being good hits and bad hits. Spin on the ball, loft of the ball (based on club selection), obstacles (hazards). Not to mention other players. The point being, proper mapping of the scope of the problem to be solved, should be the first part of the exercise. However, it is often overlooked, and thus, the problem becomes bigger than it needs to be.

     

    When I set out to develop my engine controller, My goal was to see just how little I could get away with. I proposed to a bunch of my engine experts, that I would be able to develop a suitable controller to manage ignition and fueling, for a 4, 6, or 8 cylinder motor, with TBI or direct fuel injection, and single, dual, or coil on plug configuration. I also proposed to use a simple 8 bit processor with times, EEPROM, and A/D built in (an Arduino Nano).  My friends were skeptical, and some even said it wasn't possible, they were mostly electrical and mechanical engineers.

     

    Step 1: keep it small.  Source code needed to fit on the device, so extraneous operations needed to be eliminated. Because I am a decent programmer, I determined the "Arduino" interface was to be sacrificed. Using a programmer, I would only have my code installed on the Nano. This meant, communication of information would need to be provided by my code, so a simple interface to the serial port would be the first order of business.

     

    Step 2: develop an interface protocol. Many protocols are already available, and can be implemented (including the Arduino interface), but my goal was to keep things small. All I needed was to be able to update calibration (EEPROM), and fetch monitor values. So the first architecture decision became allocation of EEPROM. A putter and getter operation for EEPROM needed to be provided. Luckily, the documentation for the Mega328 has the code, so I didn't need to write the base. I did however, want to access the memory as 16 bit integer instead of bytes, so a little modification was required. The other element was monitor access. Monitors could use the same getter method, with the difference being where the data was obtained.  So the interface protocol, only needed to be a get function with an enumerated value, and a put function with an enumerated value, and the value to save.

     

    Step 3: establish units. I said 16 bit number would be satisfactory for nearly all applications. In the previous discussion, I presented the use of binary radians where a circle is divided into 32768 degrees, The wisdom of this will be shown as I progress. For ratios, I will use a binary fixed point value where 1024 = 100%. This was selected partially for ease of conversion, but primarily because the A/Ds on the 328 are 10 bits each, so 10 bits = 1024 = 100%.  For temperature, most of the calculations are based on degrees K. With no negative values possible (can't go below 0 K) using a 16 bit unsigned value to hold temperature works out well as K * 100, so the value 0C is stored as 27315. Likewise pressure is primarily measured as kilo-Pascals absolute, and saved as kPa * 100. So 1 atmosphere is stored as 10129. It is also possible to hold values for voltage as V * 1000, but I will show that it is actually unnecessary to maintain a value of voltage at all.

     

    Step 4: provide conversions. The calibration values are all stored in tables. The size of the table is balanced by available space in EEPROM, and desired accuracy. A bigger table has more values, so provides improved accuracy. Here is where I emphasize voltage is unnecessary. When an A/D value is converted, it is converted into "counts". I can't begin to tell how many examples I have found, where the first step after reading the A/D counts is to convert the number into volts. If the manufacturer of the sensor provides a table with the value of volts to measurement, what is the problem with taking that table and converting the voltage value into counts, so the table becomes counts to measurement. Now, with this table, it is possible to read the A/D counts and look up in the table the value of the measurement, reducing two conversions, to one. Table size is now the question, because storing 1024 integers in a table for one sensor might prove a problem. The solution is to use linear interpolation and a finite number of integers. By using 8 nodes, dividing the 10 bit A/D by 128 yields the node on one end. using the left and right values and the 7 bit remainder times the difference, a very close approximation of the value can be obtained.

     

    As an example, I have created a sin and cos function where the value is multiplied by 32768. The table is 33 cells in size (0 - 32) and represents 1/4 wave. the circle uses 32768 degrees for the circumference. For n from 0 -8192 the value is read straight from the table. For n from 8192-16384 the table is read from back to front. For n from 16384-24576 the table is read backward and multiplied by -1. And for the last segment, from 24576-32768 the value is multiplied by -1.  For each of the quadrants, the two high order bits can be used with bit 13 determining which direction the table is accessed, and bit 14 determining positive or negative.  Here's the code.  (As a bonus, by adding 90 degrees or 8192 to the value you get the cos.)

     

    static const UWord sinTbl[] = {
          0,  1608,  3212,  4808,  6393,  7962,  9512, 11039
     ,12540, 14010, 15447, 16846, 18205, 19520, 20788, 22006
     ,23170, 24279, 25330, 26320, 27246, 28106, 28899, 29622
     ,30274, 30853, 31357, 31786, 32138, 32413, 32610, 32729, 32767
    };
    SWord getSin(UWord d) {
     UWord n[2];
     UWord a = (((d & 8192) == 0) ? d : ~d) & 8191;
     UWord i = a >> 8;
     n[0] = sinTbl[i]; n[1] = sinTbl[i + 1];
     i = (UWord)((((ULong)n[1] - (ULong)n[0]) * (ULong)(a & 255)) >> 5) + n[0];
     return (((d & 16384) == 0) ? i : -i);
    }
    SWord getCos(UWord d) {
     return (getSin(d + 8192));
    }

     

    Pretty nifty eh?

     

    More stuff to come tomorrow,

    Jack

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

    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 specialist. I have also had a good amount of electrical experience with circuit design.  My experience has shown me, my electrical ability is equivalent to an electrical engineer's programming ability. I am able to hash and bash my way to a hardware solution, but it lacks a level of elegance obtained by expertise.  Likewise, software from most of the electrical engineers that I have experienced, has shown many areas were elegance is a little lacking.

     

    Likewise, there have been a good number of embedded programmers I have worked with, who disprove my observation. These people have learned the lesson, "if the only tool you have is a hammer, all your problems look like nails". Electrical engineers, learn how to make state machines, and they learn how to convert sensor values from a voltage reading. Most cases, they also hold to the idea that floating point numbers solve many of the problems of accuracy.

     

    A course that was taught long ago in the early days of computers, was compiler design. I am not quite sure when it was determined that this discipline was no longer necessary, but I contend it needs to be brought back. The reason is, it provided a good means of showing what instructions did, at a low enough level, to indicate where bottlenecks would occur, and where accuracy is lost. This is where I pull out my soap box and expound the virtues of pure integer math. My premise is, for all except a very few extreme cases, floating point is unnecessary, and for easily greater than half the cases (especially for control systems) more than 16 bits is a luxury.

     

    I like to use the example of a golf course.  On a par 5 hole, the distance from the tee box, to the hole is generally around 500 yards. 500 yards is 1500 feet, which is 18000 inches. Using a 16 bit unsigned number to measure distance, the measurement is a little more than 1/3 of an inch. So it is possible to map a 500 yard field down to 1/3 of an inch using only 16 bit integers. Where elegance is incorporated... remember to always look at the problem from multiple viewpoints.

     

    With the golf problem, the distance to the hole, is actually irrelevant. The important factor is how far can the golfer actually hit the ball. If you were writing a video game to simulate golfing, The behavior is actually to hit the ball, then walk (or ride) to where the ball landed. Simulation being good hits and bad hits. Spin on the ball, loft of the ball (based on club selection), obstacles (hazards). Not to mention other players. The point being, proper mapping of the scope of the problem to be solved, should be the first part of the exercise. However, it is often overlooked, and thus, the problem becomes bigger than it needs to be.

     

    When I set out to develop my engine controller, My goal was to see just how little I could get away with. I proposed to a bunch of my engine experts, that I would be able to develop a suitable controller to manage ignition and fueling, for a 4, 6, or 8 cylinder motor, with TBI or direct fuel injection, and single, dual, or coil on plug configuration. I also proposed to use a simple 8 bit processor with times, EEPROM, and A/D built in (an Arduino Nano).  My friends were skeptical, and some even said it wasn't possible, they were mostly electrical and mechanical engineers.

     

    Step 1: keep it small.  Source code needed to fit on the device, so extraneous operations needed to be eliminated. Because I am a decent programmer, I determined the "Arduino" interface was to be sacrificed. Using a programmer, I would only have my code installed on the Nano. This meant, communication of information would need to be provided by my code, so a simple interface to the serial port would be the first order of business.

     

    Step 2: develop an interface protocol. Many protocols are already available, and can be implemented (including the Arduino interface), but my goal was to keep things small. All I needed was to be able to update calibration (EEPROM), and fetch monitor values. So the first architecture decision became allocation of EEPROM. A putter and getter operation for EEPROM needed to be provided. Luckily, the documentation for the Mega328 has the code, so I didn't need to write the base. I did however, want to access the memory as 16 bit integer instead of bytes, so a little modification was required. The other element was monitor access. Monitors could use the same getter method, with the difference being where the data was obtained.  So the interface protocol, only needed to be a get function with an enumerated value, and a put function with an enumerated value, and the value to save.

     

    Step 3: establish units. I said 16 bit number would be satisfactory for nearly all applications. In the previous discussion, I presented the use of binary radians where a circle is divided into 32768 degrees, The wisdom of this will be shown as I progress. For ratios, I will use a binary fixed point value where 1024 = 100%. This was selected partially for ease of conversion, but primarily because the A/Ds on the 328 are 10 bits each, so 10 bits = 1024 = 100%.  For temperature, most of the calculations are based on degrees K. With no negative values possible (can't go below 0 K) using a 16 bit unsigned value to hold temperature works out well as K * 100, so the value 0C is stored as 27315. Likewise pressure is primarily measured as kilo-Pascals absolute, and saved as kPa * 100. So 1 atmosphere is stored as 10129. It is also possible to hold values for voltage as V * 1000, but I will show that it is actually unnecessary to maintain a value of voltage at all.

     

    Step 4: provide conversions. The calibration values are all stored in tables. The size of the table is balanced by available space in EEPROM, and desired accuracy. A bigger table has more values, so provides improved accuracy. Here is where I emphasize voltage is unnecessary. When an A/D value is converted, it is converted into "counts". I can't begin to tell how many examples I have found, where the first step after reading the A/D counts is to convert the number into volts. If the manufacturer of the sensor provides a table with the value of volts to measurement, what is the problem with taking that table and converting the voltage value into counts, so the table becomes counts to measurement. Now, with this table, it is possible to read the A/D counts and look up in the table the value of the measurement, reducing two conversions, to one. Table size is now the question, because storing 1024 integers in a table for one sensor might prove a problem. The solution is to use linear interpolation and a finite number of integers. By using 8 nodes, dividing the 10 bit A/D by 128 yields the node on one end. using the left and right values and the 7 bit remainder times the difference, a very close approximation of the value can be obtained.

     

    As an example, I have created a sin and cos function where the value is multiplied by 32768. The table is 33 cells in size (0 - 32) and represents 1/4 wave. the circle uses 32768 degrees for the circumference. For n from 0 -8192 the value is read straight from the table. For n from 8192-16384 the table is read from back to front. For n from 16384-24576 the table is read backward and multiplied by -1. And for the last segment, from 24576-32768 the value is multiplied by -1.  For each of the quadrants, the two high order bits can be used with bit 13 determining which direction the table is accessed, and bit 14 determining positive or negative.  Here's the code.  (As a bonus, by adding 90 degrees or 8192 to the value you get the cos.)

     

    static const UWord sinTbl[] = {
          0,  1608,  3212,  4808,  6393,  7962,  9512, 11039
     ,12540, 14010, 15447, 16846, 18205, 19520, 20788, 22006
     ,23170, 24279, 25330, 26320, 27246, 28106, 28899, 29622
     ,30274, 30853, 31357, 31786, 32138, 32413, 32610, 32729, 32767
    };
    SWord getSin(UWord d) {
     UWord n[2];
     UWord a = (((d & 8192) == 0) ? d : ~d) & 8191;
     UWord i = a >> 8;
     n[0] = sinTbl[i]; n[1] = sinTbl[i + 1];
     i = (UWord)((((ULong)n[1] - (ULong)n[0]) * (ULong)(a & 255)) >> 5) + n[0];
     return (((d & 16384) == 0) ? i : -i);
    }
    SWord getCos(UWord d) {
     return (getSin(d + 8192));
    }

     

    Pretty nifty eh?

     

    More stuff to come tomorrow,

    Jack

    • Cancel
    • Vote Up +2 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