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 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 63 replies
  • Subscribers 46 subscribers
  • Views 4146 views
  • Users 0 members are here
Related

Engine Management

jack.chaney56
jack.chaney56 over 9 years ago

Hi Ben,

I am a programmer with a very small amount of skill with circuits, and am looking to create a platform for an engine management system, using an Arduino Mega 2560. I had done a bit of the coding, when I ran into some timing issues with the built in Arduino manager, so I switched over and started using AVR Studio and a programmer to go directly to the chip itself.  The code looks like it should work ok, but now I need some additional circuits to handle the energy levels of coils and injectors (Something like IGBTs). Sensors are being run through simple dividers (no protection yet), and cam and crank inputs are through a simple comparitor

 

Let me know what you think,

Jack

  • Sign in to reply
  • Cancel

Top Replies

  • jack.chaney56
    jack.chaney56 over 7 years ago +2
    Back again... After a bit of time away seeking enlightenment (and a steady paycheck), I am ready to get back to work on my project. I have continued to play around with the code and a number of components…
  • jack.chaney56
    jack.chaney56 over 7 years ago +2
    I want to start this thing right, so the shopping list for people that want to play along at home: Raspberry Pi - version is not significant if you don't mind a slow response when using Eclipse, but 3B…
  • jack.chaney56
    jack.chaney56 over 7 years ago +2
    Start off with two things. First, I forgot (neglected) to provide instruction on how to get the compiled code onto the Nano. Fault of familiarity; having done the process so many times, I had shifted to…
Parents
  • jack.chaney56
    jack.chaney56 over 7 years ago

    Revelation time...

     

    When I was working on the design for the model, I was continuously looking for areas where I could reduce resources by recognizing patterns.  I knew there were cases with coil on plug, where there was an overlap of coils (more than one coil on at a time), that could occur at high RPM values when the dwell was long enough to cause necessity for more than one coil to be charging at once. While doing this study, and trying to figure out if I needed a timer for each coil, I realized, there was not a state where more than one coil turned on at a time, or turned off at the same time. The result meant only two timer compare registers were needed, one that only turns the coil on, and the other that only turns the coil off.  The advantage being, the only task the one interrupt had to focus on, was to turn on the identified coil the other task is to turn the interrupt enable off.

     

    The size of the timer register limits the length of time from setting the register to when the event occurs. With the timer tic, on our system, running at 2MHz, the 16 bit limit is around 32mS. At higher RPM with large tooth count, this can be acceptable. However, since the audience for this system is a smaller platform (4 cylinder, 4x crank), when RPM drops below 460. As an operational number this would seem to not be an issue, however, the case of startup is important. During cranking, the RPM is very low, and it is a good feature to capture timing and start the motor in as few cranks as possible. So, having the ability to set timing on very low RPM is a desirable feature. The solution is to use the 1mS timer tic with a pair of countdown timers, one for the coil on, one for the coil off.

     

    In the operation for setting the timers, a threshold is tested to see if it is better to use the 1mS timer, or just set the compare register. This value can be anything up to 30mS, however, a reasonable value that I used was just 3mS.  The variables used are elapseTime - which is the calculated time from the previous tooth event to the coil event, baseTime - which is the crkTime value of the last tooth event, and the number of the coil to activate.

     

    First the value of the timer is factored

     

         eventTime = elapseTime + baseTime;

     

    Second a check of amount of time until the event (the value of elapseTime is a 32 bit number).

     

         if (elapseTime > THREE_mS) { /* set the mS timeout */ } /* some math is needed here, but the advantage is the motor is turning slowly so more time is available */

         else { /* set the timer compare directly and enable the interrupt */ }

     

    Third let the interrupt know which coil to set

     

         activeCoil = coil;

     

    With that in place, what values should be used for angle. Before we get too much into ignition management, it is better to put something simple in place first to test out the system. The tach signal is a generally accepted operation, and has generally static values. A tach signal is a pulse that occurs once for each cylinder, and is usually timed so the active edge is aligned with the TDC of the cylinder. This means more variables are identified one for the number of cylinders, and then an array with the angle for each TDC. For the design of the system, there also needs to be an inactive edge or leading edge, so a second array. The simple method to obtain the leading edge is to just use a constant angle and subtract from the active edge angle. Five degrees is a satisfactory value to use for this.

     

    The angle of TDC does not change during operation, so it is a good candidate for setting the TDC values during engine off. This also means, when the engine is off, number of cylinders can be configured as well as firing sequence (calibration information). Reading the calibration into the array is done during engine off. I think that will be enough for now. The variables used are number of cylinders (cylCt), and the two arrays tach event or active edge (tkEv) and tach leading edge (tkSt).  From before, there is min and max which are the window angle for comparison, event time (et), base time (bt), past angle or the angle of the last tooth (pa), and the magic number (base).  Code for the scheduler:

    /**************************************************************************************************
    Parameters:
       min/max - start and end angle of window for sample
       pa - angle of last event, last tooth
       bs - magic BASE
       tm - time of last event, last tooth
    **************************************************************************************************/
    void schedule(UWord min, UWord max, UWord pa, SLong bs, SLong tm) {
     SLong ft;
     UByte c;
    
     if (min < max) {
      for (c = 0; c < cylCt; c++) {
       if ((min < tkSt[c]) && (tkSt[c] <= max)) { ft = (SLong)(tkSt[c] - pa) * bs / PART2; ignTkHi(ft, tm); }
       if ((min < tkEv[c]) && (tkEv[c] <= max)) { ft = (SLong)(tkEv[c] - pa) * bs / PART2; ignTkLo(ft, tm); }
     } else {
      for (c = 0; c < spEvnt; c++) {
       if ((min < tkSt[c]) || (tkSt[c] <= max)) { ft = (SLong)(tkSt[c] - pa) * bs / PART2; ignTkHi(ft, tm); }
       if ((min < tkEv[c]) || (tkEv[c] <= max)) { ft = (SLong)(tkEv[c] - pa) * bs / PART2; ignTkLo(ft, tm); }
     }

     

    The two routines that set the information for the timers are

    UWord etTkHi, etTkLo;
    SWord dlyTkHi, dlyTkLo;
    
    /**************************************************************************************************
    Generated TACH signal
    **************************************************************************************************/
    void ignTkHi(SLong et, SLong bt) {
     SLong ev = et + bt;               /* get the timer event compare value */
    
     etTkHi = (UWord)(ev & 0xffff);    /* the value is masked to 16 bits to match the compare register */
     if (et > THREE_mS) {              /* check for long delay and set 1ms timeout */
      dlyTkHi = (SWord)((et - TWO_mS) / TICS_PER_mS);
     } else {                          /* no delay */
      OCR1A = etTkHi;                  /* set the timer compare register */
      TIMSK1 |= (1 << OCIE1A);         /* and enable the interrupt */
     }
    }
    void ignTkLo(SLong et, SLong bt) {
     SLong ev = et + bt;               /* get the timer event compare value */
    
     etTkLo = (UWord)(ev & 0xffff);    /* the value is masked to 16 bits to match the compare register */
     if (et > THREE_mS) {              /* check for long delay and set 1ms timeout */
      dlyTkLo = (SWord)((et - TWO_mS) / TICS_PER_mS);
     } else {                          /* no delay */
      OCR1B = etTkLo;                  /* set the timer compare register */
      TIMSK1 |= (1 << OCIE1B);         /* and enable the interrupt */
     }
    }

     

    The addition to the timers is not too difficult.

    /**************************************************************************************************
     Timer 1 is a 16 bit timer running fast
     Timer 1 group is used for coil action A on B off
     Activity for both is to perform the operation and disable the interrupt
    **************************************************************************************************/
    ISR(TIMER1_COMPA_vect) { TIMSK1 &= ~(1 << OCIE1A); setTach(true);  }
    ISR(TIMER1_COMPB_vect) { TIMSK1 &= ~(1 << OCIE1B); setTach(false); }

     

    and in the 1mS tic...

    ISR(TIMER0_COMPA_vect) {
    ...
     if (dlyTkHi > 0) { --dlyTkHi; if (dlyTkHi == 0) { OCR1A = etTkHi; TIMSK1 |= (1 << OCIE1A); } }
     if (dlyTkLo > 0) { --dlyTkLo; if (dlyTkLo == 0) { OCR1B = etTkLo; TIMSK1 |= (1 << OCIE1B); } }
    ...
    }

     

    Last the update to the engine off operation

    ...
      cylCt = (UByte)getCylCount();
      for (c = 0; c < cylCt; c++) {
        tkEv[c] = getCylAngle(c);
        tkSt[c] = tkEv[c] - FIVE_DEG;
      }
    ...

     

    That's it for now. In the next installment, I will provide full listings instead of the patchwork.

     

    Enjoy,

    Jack

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

    Revelation time...

     

    When I was working on the design for the model, I was continuously looking for areas where I could reduce resources by recognizing patterns.  I knew there were cases with coil on plug, where there was an overlap of coils (more than one coil on at a time), that could occur at high RPM values when the dwell was long enough to cause necessity for more than one coil to be charging at once. While doing this study, and trying to figure out if I needed a timer for each coil, I realized, there was not a state where more than one coil turned on at a time, or turned off at the same time. The result meant only two timer compare registers were needed, one that only turns the coil on, and the other that only turns the coil off.  The advantage being, the only task the one interrupt had to focus on, was to turn on the identified coil the other task is to turn the interrupt enable off.

     

    The size of the timer register limits the length of time from setting the register to when the event occurs. With the timer tic, on our system, running at 2MHz, the 16 bit limit is around 32mS. At higher RPM with large tooth count, this can be acceptable. However, since the audience for this system is a smaller platform (4 cylinder, 4x crank), when RPM drops below 460. As an operational number this would seem to not be an issue, however, the case of startup is important. During cranking, the RPM is very low, and it is a good feature to capture timing and start the motor in as few cranks as possible. So, having the ability to set timing on very low RPM is a desirable feature. The solution is to use the 1mS timer tic with a pair of countdown timers, one for the coil on, one for the coil off.

     

    In the operation for setting the timers, a threshold is tested to see if it is better to use the 1mS timer, or just set the compare register. This value can be anything up to 30mS, however, a reasonable value that I used was just 3mS.  The variables used are elapseTime - which is the calculated time from the previous tooth event to the coil event, baseTime - which is the crkTime value of the last tooth event, and the number of the coil to activate.

     

    First the value of the timer is factored

     

         eventTime = elapseTime + baseTime;

     

    Second a check of amount of time until the event (the value of elapseTime is a 32 bit number).

     

         if (elapseTime > THREE_mS) { /* set the mS timeout */ } /* some math is needed here, but the advantage is the motor is turning slowly so more time is available */

         else { /* set the timer compare directly and enable the interrupt */ }

     

    Third let the interrupt know which coil to set

     

         activeCoil = coil;

     

    With that in place, what values should be used for angle. Before we get too much into ignition management, it is better to put something simple in place first to test out the system. The tach signal is a generally accepted operation, and has generally static values. A tach signal is a pulse that occurs once for each cylinder, and is usually timed so the active edge is aligned with the TDC of the cylinder. This means more variables are identified one for the number of cylinders, and then an array with the angle for each TDC. For the design of the system, there also needs to be an inactive edge or leading edge, so a second array. The simple method to obtain the leading edge is to just use a constant angle and subtract from the active edge angle. Five degrees is a satisfactory value to use for this.

     

    The angle of TDC does not change during operation, so it is a good candidate for setting the TDC values during engine off. This also means, when the engine is off, number of cylinders can be configured as well as firing sequence (calibration information). Reading the calibration into the array is done during engine off. I think that will be enough for now. The variables used are number of cylinders (cylCt), and the two arrays tach event or active edge (tkEv) and tach leading edge (tkSt).  From before, there is min and max which are the window angle for comparison, event time (et), base time (bt), past angle or the angle of the last tooth (pa), and the magic number (base).  Code for the scheduler:

    /**************************************************************************************************
    Parameters:
       min/max - start and end angle of window for sample
       pa - angle of last event, last tooth
       bs - magic BASE
       tm - time of last event, last tooth
    **************************************************************************************************/
    void schedule(UWord min, UWord max, UWord pa, SLong bs, SLong tm) {
     SLong ft;
     UByte c;
    
     if (min < max) {
      for (c = 0; c < cylCt; c++) {
       if ((min < tkSt[c]) && (tkSt[c] <= max)) { ft = (SLong)(tkSt[c] - pa) * bs / PART2; ignTkHi(ft, tm); }
       if ((min < tkEv[c]) && (tkEv[c] <= max)) { ft = (SLong)(tkEv[c] - pa) * bs / PART2; ignTkLo(ft, tm); }
     } else {
      for (c = 0; c < spEvnt; c++) {
       if ((min < tkSt[c]) || (tkSt[c] <= max)) { ft = (SLong)(tkSt[c] - pa) * bs / PART2; ignTkHi(ft, tm); }
       if ((min < tkEv[c]) || (tkEv[c] <= max)) { ft = (SLong)(tkEv[c] - pa) * bs / PART2; ignTkLo(ft, tm); }
     }

     

    The two routines that set the information for the timers are

    UWord etTkHi, etTkLo;
    SWord dlyTkHi, dlyTkLo;
    
    /**************************************************************************************************
    Generated TACH signal
    **************************************************************************************************/
    void ignTkHi(SLong et, SLong bt) {
     SLong ev = et + bt;               /* get the timer event compare value */
    
     etTkHi = (UWord)(ev & 0xffff);    /* the value is masked to 16 bits to match the compare register */
     if (et > THREE_mS) {              /* check for long delay and set 1ms timeout */
      dlyTkHi = (SWord)((et - TWO_mS) / TICS_PER_mS);
     } else {                          /* no delay */
      OCR1A = etTkHi;                  /* set the timer compare register */
      TIMSK1 |= (1 << OCIE1A);         /* and enable the interrupt */
     }
    }
    void ignTkLo(SLong et, SLong bt) {
     SLong ev = et + bt;               /* get the timer event compare value */
    
     etTkLo = (UWord)(ev & 0xffff);    /* the value is masked to 16 bits to match the compare register */
     if (et > THREE_mS) {              /* check for long delay and set 1ms timeout */
      dlyTkLo = (SWord)((et - TWO_mS) / TICS_PER_mS);
     } else {                          /* no delay */
      OCR1B = etTkLo;                  /* set the timer compare register */
      TIMSK1 |= (1 << OCIE1B);         /* and enable the interrupt */
     }
    }

     

    The addition to the timers is not too difficult.

    /**************************************************************************************************
     Timer 1 is a 16 bit timer running fast
     Timer 1 group is used for coil action A on B off
     Activity for both is to perform the operation and disable the interrupt
    **************************************************************************************************/
    ISR(TIMER1_COMPA_vect) { TIMSK1 &= ~(1 << OCIE1A); setTach(true);  }
    ISR(TIMER1_COMPB_vect) { TIMSK1 &= ~(1 << OCIE1B); setTach(false); }

     

    and in the 1mS tic...

    ISR(TIMER0_COMPA_vect) {
    ...
     if (dlyTkHi > 0) { --dlyTkHi; if (dlyTkHi == 0) { OCR1A = etTkHi; TIMSK1 |= (1 << OCIE1A); } }
     if (dlyTkLo > 0) { --dlyTkLo; if (dlyTkLo == 0) { OCR1B = etTkLo; TIMSK1 |= (1 << OCIE1B); } }
    ...
    }

     

    Last the update to the engine off operation

    ...
      cylCt = (UByte)getCylCount();
      for (c = 0; c < cylCt; c++) {
        tkEv[c] = getCylAngle(c);
        tkSt[c] = tkEv[c] - FIVE_DEG;
      }
    ...

     

    That's it for now. In the next installment, I will provide full listings instead of the patchwork.

     

    Enjoy,

    Jack

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • Cancel
Children
  • jack.chaney56
    jack.chaney56 over 7 years ago in reply to jack.chaney56

    ...please note, tach signal is used as an example here. On the Nano, since there is only one 16 bit timer, I will be using it to manage ignition (the most time critical operation), I will be presenting a creative solution for managing slightly less time critical events for tach and fuel. The limited resources of the Nano provided a need for solving problems that larger processors don't encounter. (The truth will set you free).  The other problem to solve was, since there are up to 8 coils, and 8 injectors, along with some idiot lights (MIL, shift, etc.), the output lines needed to be expanded.  I'll show my solution for that too.

     

    I also made a version of the same code to run on a Mega2560. It has 4 16 bit timers, so each of the event managers is nearly identical for coils, injectors, and tach. The 2560 also has address mapping, so  outboard devices are easier to run. The exercise here was to minimize the code that was unique between the two platforms. Got it down to 4 files that are different though very similar. All the rest are shared between the two.

     

    Jack

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • Cancel
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