63 Replies Latest reply on Sep 6, 2018 3:41 PM by jack.chaney56

    Engine Management

    jack.chaney56

      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

        • Re: Engine Management
          jack.chaney56

          Additional information...

          I can provide a bit of background on engine management strategy, and what goes into making of engine controller code. The timing operations, and what needs to happen when. How to work with single coil or coil on plug. Using normally aspirated, throttle body or direct injection of fuel.

            • Re: Engine Management
              jack.chaney56

              Dear me, The gauntlet has been dropped. I was telling a friend about my idea and what I was using, and got the response I was hoping to hear. He was telling a third party that does work for a "major auto manufacturer" and was told the idea it was preposterous, and it would never work.

               

              So now I have a problem, what do I do with code that can take input from potentiometers acting as MAP, ECT, and IAT, and simulated cam (single pulse, half moon, VVT, and Mopar 4,6, and 8) and crank signals (4x to 60-2), and provides timing for up to 8 cylinders (single, dual, or coil on plug) from 150 up to 10000RPM using an Arduino Nano.  Unless, he thought I needed more hardware, like IGBTs or a CD charge pump or something, because I do need that part.  But from what I got from my friend, the third party didn't think the Nano was up to the task. We shall see.

                • Re: Engine Management
                  gazone

                  Jack keep updating us on your interesting work!

                  1 of 1 people found this helpful
                    • Re: Engine Management
                      jack.chaney56

                      Hi Gary,

                      Thank you for the encouragement. I will be publishing a short series in the documents section that covers elements of engine management. The current engine controllers use a "just in time" kind of methodology, which sometimes has to play catch up, and can get a little confused. My method operates off a different process, what messed up a friend was when I said my process can use a 4x wheel to run a 6 cylinder motor, as long as I can detect TDC0, everything else is just more detail.

                       

                      Jack.

                      • Re: Engine Management
                        jack.chaney56

                        Hi Gary,

                        I posted the start of my first article, for lack of a better location, in the Arduino section.  I used a Mega 2560 for my initial platform.

                         

                        Jack

                          • Re: Engine Management
                            michaelkellett

                            Hello Jack,

                             

                            I can't find that article - can you give us a link.

                             

                            It's  a long time since I worked on automotive and even then it was never directly on engine management although controllers I built had to talk to engine management systems and I haven't totally lost touch.  There is no way an Arduino nano can replicate current, legislation compliant, engine management systems - since not only do you need to control the engine and emissions (or fake it like VW) but you need to detect and log misfires and faults. The tiny processor in the nano doesn't have enough ROM for a basic map and certainly doesn't have the processing power to calculate it on the fly.

                             

                            You might be able to replicate a traditional distributor and centrifugal advance system with a nano - but why bother ?

                             

                            MK

                    • Re: Engine Management
                      jack.chaney56

                      Updates. forgot to post here, I have completed 4 installments for the Engine Management in the Arduino section.

                      • Re: Engine Management
                        jack.chaney56

                        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. First, I wanted to have a more interesting development workstation, so I thought it would be fun to use a Raspberry Pi. After installing the latest version of Raspbian and getting all the updates, I loaded the AVR tool set, then Eclipse and the C/C++ components. Then adding the AVR plugin and whoopee I had a workstation.

                         

                        I also really wanted to use the Nano for a platform but had problems with adding controls for 8 cylinder coil on plug and still be able to have outputs for solenoids and idiot lights (shift, MIL, etc). Then I thought of using 4 of the port lines to control a 74LS138 and using a bunch of 74LS75s and some inverters, route the other 4 port lines available to create a multiplexed switch network. Got it to light LEDs and the scope signals look right too.  The last part of the puzzle for the Nano was since the 328p is limited for timers, I got inventive for some of the timing pieces (fueling is less time critical).

                         

                        So I created a dual path, with the majority of software files being shared between both platforms, only when hardware forces there to be a difference, the same code runs on both Mega2560 and Nano.  I will attempt to provide all the how-to for the development workstation, the Nano setup and programming, and source code with design theory.

                         

                        Since much stuff has been refined over the course of time, I will probably be redoing a bunch of stuff and there will probably be a bunch of  repeat.  For anyone interested, it is possible to get a big dose of what is to follow by looking over the early work at Engine Management project

                         

                        I am now in the throws of developing the UI to provide the calibration in a more comfortable way. Again, trying to stay with using a Raspberry Pi as the UI platform too.

                         

                        Jack

                          • Re: Engine Management
                            14rhb

                            Hi Jack,

                             

                            I've just come into this thread but see it is over two years old and someone initially told you it couldn't be done....well my philosophy in life is that if someone else managed to make X in the past then it should be possible to make one myself (sometimes this doesn't work due to limited finances, time, materials, legality or equipment; but in theory nothing should hold you back).

                             

                            Look forward to your updates now you are back on track.

                             

                            Rod

                            • Re: Engine Management
                              rsc

                              Hi Jack,

                              Interesting project for a single software developer. When I worked at Chrysler in the engine controls group they needed over 30 people to code, calibrate and develop hardware for the SBEC controller.

                              Good luck,

                              Scott

                                • Re: Engine Management
                                  jack.chaney56

                                  Hi Scott,

                                   

                                  Were you on the second floor over the Dyno Cells?

                                   

                                  Jack

                                    • Re: Engine Management
                                      rsc

                                      Hi Jack,

                                      I started downtown, then moved to Auburn Hills 3rd floor engine and chassis controls for large car platform.  The coolest toy they had was the engine simulator cube.

                                      Scott

                                        • Re: Engine Management
                                          jack.chaney56

                                          Nice, so you knew Fred, Ron, Dale, and Bob, or were you after that (probably yes if you came from HP).

                                           

                                          Jack

                                          ps. Trying to keep names and details obscure to protect the innocent.

                                            • Re: Engine Management
                                              rsc

                                              I'm trying to think of who Fred is...…...I worked for Kissel, then Cwiek.

                                              I remember watching them build the first Dodge Viper in the plastics shop on a dune buggy frame in HP.

                                              Scott

                                                • Re: Engine Management
                                                  jack.chaney56

                                                  Hi Scott,

                                                  Came over from AMC/Jeep (Plymouth Rd). Fred was Meisterfeld. and Dale Koch. Was part of Neon Launch (dare to be different). Then went over to IT to work on DRBIII.

                                                   

                                                  Jack

                                                  • Re: Engine Management
                                                    jack.chaney56

                                                    Hi again,

                                                     

                                                    Back to your first statement, about volume of work for a single programmer.  I guess it might be... but I am an old, retired, programmer. This is a good exercise to see if it really can be done.

                                                     

                                                    My problem is, I do need help with the hardware stuff (circuit design and layout). My stuff is all using breadboard, LEDs, and some Rube Goldberg mechanical devices.  I think I have the software working, but would really appreciate some partnership assistance from time to time

                                                     

                                                    Jack

                                                      • Re: Engine Management
                                                        rsc

                                                        Hardware is usually vehicle specific, what is your target platform?

                                                          • Re: Engine Management
                                                            jack.chaney56

                                                            Funny you should say that. The truth is the hardware is pretty much portable. The difference for system timing comes down to the cam and crank wheels and the signals they produce. I have software that does the cam decoding for simple pulse or half moon, Mopar 4/6/or 8 cylinder, and GM VVT. The crank signal is based on the call out for how many teeth, and if there are filled or missing teeth. The other parameters are for injector size and the variety of sensors. I had similar code operating a GM LS, a MOPAR SRT, and a Ford Cobra (with multi-strike CD), The input signals might need some conditioning for hall effect, I will need to see about that. My focus is primarily on the coding side of the process.

                                                             

                                                            The primary target for the controller project is actually Porsche and VW 4 cyl, and Corvair 6 cyl. But I have built the code and my breadboard to run up to 8 cylinder COP, with direct sequential injection.

                                                             

                                                            Jack

                                            • Re: Engine Management
                                              jack.chaney56

                                              The structure of the information, to follow, will be:

                                              Timing - For whatever is being done with the motor, either ignition or fueling, it is necessary to know the current angle, and the angular velocity. This is obtained using the CAM and CRANK input signals.

                                              Scheduling - Knowing location and velocity, it is possible to predict time to events.

                                                                       The coils need to be charged and ignition has to occur at a specific time

                                                                       The fuel injector solenoids need to open and close to supply the proper volume of fuel.

                                              Calculation - Ignition and Fueling.

                                                                       Ignition requires specific timing for angle of advancement, and start of dwell.

                                                                       Fueling calculates the quantity of fuel and depends on mass air flow combined with target air fuel ratio.

                                              Sensors - The calculated values depend on measured values during operation.  Using A/Ds and providing conversions.

                                                                       Sensors provide measurements for pressure, temperature, throttle position and other values at different locations in the system.

                                              Steady State vs Asynchronous behaviors - changes in load or demand, and making quick adjustments to improve performance. Math tricks and creative use of units.

                                              Calibration - Providing adjustments to "tune" the operation and optimize behavior for whatever is required.

                                              Monitoring - communication of operating parameters

                                              1 of 1 people found this helpful
                                                • Re: Engine Management
                                                  jack.chaney56

                                                  Checking back to make sure I am covering things the way I said, and so far I haven't strayed too far. The only thing I skipped was the part about injectors. This is was because the only thing different about injectors is the schedule. There are different strategies with injectors, they are; throttle body injection (TBI), batch, staggered batch, sequential, and staged.

                                                   

                                                  TBI is the simplest, and is quite similar to normal aspiration. Normal aspiration is when the fuel is drawn into the carburettor by the intake vacuum, and controlled by a needle valve. With throttle body injection, the injector shoots the fuel into the intake chamber.  With normal aspiration, the flow is continuous, with TBI, it is pulsed to control the volume of fuel, and with TBI it is pulsed four times per cam cycle. Batch is when the injectors are mounted along the intake manifold, closer to the cylinder intake, and usually an injector for each cylinder. Batch means all the injectors open at the same time, twice per cam cycle. Staggered fire, is the same as batch, except the injectors are divided into two groups, and each group fires together, twice per cam cycle, but out of phase so half then the other half then the first again, etc.

                                                   

                                                  The last two are for performance systems, again with injectors close to the cylinder intake (or inside the cylinder). Sequential means the fuel is supplied during the intake fueling stroke of the cylinder and needs to be timed accordingly. Since it is sequential, each injector operates independently, and only fires once per cam cycle. Staged fueling is again, for high performance systems when the demand for fuel exceeds what can be supplied by a single injector. In these cases, a second bank of injectors is used to increase the volume. The timing follows the pattern of sequential injection.

                                                   

                                                  Without getting into volume of fuel (yet), it is possible to see the scheduling of fuel injections becomes just another event pair to open and close the injectors, either individually or as a group. The timing can be associated again with an angle of event.  There is lots more to fueling, which will be covered, but I hope this provides how much of the work is already in place from the ignition timing operation.

                                                   

                                                  Jack

                                                • Re: Engine Management
                                                  jack.chaney56

                                                  I will also have configuration steps for making your own workstation and development platform for anyone that wants to play at home :-)

                                                  • Re: Engine Management
                                                    jack.chaney56

                                                    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+ if this is a new set up

                                                    - all the stuff to make a RPi a working desktop (SD card, keyboard, mouse, monitor, power supply, and cabling)

                                                    - internet connection is also handy, but not mandatory for the workstation after configuration.

                                                    Breadboard(s) - the usual kind so outboard components can get attached.

                                                    Arduino Nano - this will be the controller platform for the project

                                                    Arduino Nano

                                                    Programmer - Pretty much any programmer for AVR that has the 6pin connector

                                                    That is pretty much it for the moment, Probably want to get two of the Atmel units. I have a plan to use one as a signal generator for cam and crank signals.

                                                     

                                                    I'll leave the description of initial set up of the Raspberry Pi to the experts, just go through the basic steps to get a version of Raspbian up and running.

                                                    Then do the obligatory sudo apt-get update; sudo apt-get -y upgrade step.

                                                    Install the AVR components (using the installer type AVR in the search box)

                                                    - gcc-avr

                                                    - gdb-avr

                                                    - avr-libc

                                                    - binutils-avr

                                                    - avrdude

                                                    - debug symbols for avrdude

                                                    Next install Eclipse IDE (using the installer type eclipse in the search box)

                                                    - Extensible Tool Platform (eclipse)

                                                    - C/C++ Development tools for Eclipse (eclipse-cdt)

                                                     

                                                    Last add the AVR plugin to Eclipse. First start Eclipse and agree to the workspace popup. Next go to:

                                                         Help->Install New Software

                                                    In the work box, enter: http://avr-eclipse.sourceforge.net/updatesite/ and say Add, and when asked name it AVR and say OK

                                                    Next window click the checkbox for the offering and select Next

                                                    Keep agreeing with all the boxes, accept the license agreement and finish.

                                                    You will need to restart Eclipse when asked.

                                                     

                                                    It might be a good idea to be sure you have a good text editor (this I will leave to you, as text editors are very personal attachments).

                                                    It is also a good idea to install a configuration management system like GIT or Mecurial and add the Eclipse plugin (GIT is already on Raspbian).

                                                     

                                                    Once things are up and running, it is always nice to test things out with a "Hello, world!" example.

                                                    First start Eclipse, and after it starts, from the menu bar select File->New->Project Then select the type of project, C++ Project and say Next (not Finish)

                                                    This is for an AVR project, so in the next window select AVR Cross Target Application->Empty Project name the project appropriately, then say Next (again, not Finish)

                                                    Say Next for the debug selections.

                                                    Last, select the processor and speed, the processor can be selected from the dropdown, for the Nano, it is an ATMega328p, and the MCU Frequency is 16000000Hz.

                                                     

                                                    Since this is the first project, it will say "Due to selection, it is appropriate to use the C/C++ interface" and "is this OK?"; just agree.

                                                     

                                                    In the icon bar at the top (middle) there is a box with a C on it. This runs the wizard to make a C/C++ file, just remember to include the .c extension.

                                                    In the open file, type the usual...

                                                    #include <stdio.h>

                                                     

                                                    int main(void) {

                                                     

                                                         printf("Hello, world!\n");

                                                         return 0;

                                                    }

                                                     

                                                    Save the file and build using the little hammer icon in the icon bar.

                                                     

                                                    ...hopefully you can now say "Ta Da!"

                                                     

                                                    More to come,

                                                    Jack

                                                    1 of 1 people found this helpful
                                                    • Re: Engine Management
                                                      jack.chaney56

                                                      Next part to keep your interest...

                                                      Three files and a modify of the main from before.

                                                      First one, because coding standards don't tend to be standard, I like to have a types.h file so I can work without having to think too much.

                                                       

                                                      /***************************************************************************************************
                                                       All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                       another location or form is purely coincidental. The code presented caries with it no guarantee
                                                       outside my statements that IT WORKED FOR ME.
                                                       
                                                       If, in the future this code is used in any products, please provide proper recognition for my
                                                       efforts.
                                                       
                                                       Jack Chaney, 2018 Chaney Firmware
                                                       
                                                       This is the base include file, that pulls in all the AVR components, and standard items to make
                                                       life easier. I also declare some items for variable naming that makes it easier for me
                                                       to run on auto pilot.
                                                      ***************************************************************************************************/
                                                      #ifndef _TYPES_H
                                                      #define _TYPES_H
                                                      #include <stdbool.h>
                                                      #include <stdio.h>
                                                      #include <avr/interrupt.h>
                                                      #include <avr/io.h>
                                                      
                                                      typedef unsigned char  UByte;
                                                      typedef char           SByte;
                                                      typedef unsigned short UWord;
                                                      typedef short          SWord;
                                                      typedef unsigned long  ULong;
                                                      typedef long           SLong;
                                                      
                                                      /* Hardware interfacers */
                                                      #define REG_B(a)      *((volatile UByte* const)(a))
                                                      #define REG_W(a)      *((volatile UWord* const)(a))
                                                      #define REG_L(a)      *((volatile ULong* const)(a))
                                                      #define REG_Bs(a)     *((volatile SByte* const)(a))
                                                      #define REG_Ws(a)     *((volatile SWord* const)(a))
                                                      #define REG_Ls(a)     *((volatile SLong* const)(a))
                                                      #define ARR_B(a)      ((UByte* const)(a))
                                                      #define ARR_W(a)      ((UWord* const)(a))
                                                      #define ARR_L(a)      ((ULong* const)(a))
                                                      #define ARR_Bs(a)     ((SByte* const)(a))
                                                      #define ARR_Ws(a)     ((SWord* const)(a))
                                                      #define ARR_Ls(a)     ((SLong* const)(a))
                                                      
                                                      /* some use while(1) but I found this way compiles to fewer instructions */
                                                      #ifdef forever
                                                      #undef forever
                                                      #endif
                                                      #define forever    for(;;)
                                                      
                                                      /* handy bits */
                                                      #define bit0    1
                                                      #define bit1    2
                                                      #define bit2    4
                                                      #define bit3    8
                                                      #define bit4    16
                                                      #define bit5    32
                                                      #define bit6    64
                                                      #define bit7    128
                                                      #define bit8    256
                                                      #define bit9    512
                                                      #define bit10    1024
                                                      #define bit11    2048
                                                      #define bit12    4096
                                                      #define bit13    8192
                                                      #define bit14    16384
                                                      #define bit15    32768
                                                      
                                                      #endif
                                                      /* end of file */
                                                      
                                                      

                                                       

                                                      The second file is a simple configuration for the timers, to provide two components; a mS time base, and a running clock.

                                                      /***************************************************************************************************
                                                       All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                       another location or form is purely coincidental. The code presented caries with it no guarantee
                                                       outside my statements that IT WORKED FOR ME.
                                                       
                                                       If, in the future this code is used in any products, please provide proper recognition for my
                                                       efforts.
                                                       
                                                       Jack Chaney, 2018 Chaney Firmware
                                                      ***************************************************************************************************/
                                                      #include "types.h"
                                                      
                                                      #define TIMSK0_INIT  (1<<OCIE0A)
                                                      #define TIMSK2_INIT  0
                                                      #define TIMSK1_INIT  (1<<TOIE1)
                                                      #define TCCR0A_INIT  0
                                                      #define TCCR0B_INIT  (3<<CS00)
                                                      #define TCCR2A_INIT  0
                                                      #define TCCR2B_INIT  (3<<CS00)
                                                      #define TCCR1A_INIT  0
                                                      #define TCCR1B_INIT  (2<<CS10)
                                                      #define TCCR1C_INIT  0
                                                      
                                                      #define mS_UPDATE_8  250
                                                      
                                                      SLong ov0Tic;
                                                      SLong ov2Tic;
                                                      SLong ov1Tic;
                                                      SLong getTime1(void) { ULong rVal; cli(); rVal = (ov1Tic | TCNT1); sei(); return rVal; }
                                                      
                                                      void initTimers(void) {
                                                       TCCR0A = TCCR0A_INIT; TCCR0B = TCCR0B_INIT; TIMSK0 = TIMSK0_INIT;
                                                       TCCR2A = TCCR2A_INIT; TCCR2B = TCCR2B_INIT; TIMSK2 = TIMSK2_INIT;
                                                       TCCR1A = TCCR1A_INIT; TCCR1B = TCCR1B_INIT; TCCR1C = TCCR1C_INIT; TIMSK1 = TIMSK1_INIT;
                                                      }
                                                      
                                                      ISR(TIMER0_COMPA_vect) {
                                                       OCR0A += mS_UPDATE_8;  /* refresh for 1mS Timer Tic */
                                                      }
                                                      
                                                      ISR(TIMER0_OVF_vect) { ov0Tic = (ov0Tic +   256L) & 0x07ffff00; }
                                                      ISR(TIMER2_OVF_vect) { ov2Tic = (ov2Tic +   256L) & 0x07ffff00; }
                                                      ISR(TIMER1_OVF_vect) { ov1Tic = (ov1Tic + 65536L) & 0x3fff0000; }
                                                      /* end of file */
                                                      
                                                      

                                                       

                                                      The third uses the 328 interrupt inputs, which will be the cam and crank input signals.

                                                      /***************************************************************************************************
                                                       All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                       another location or form is purely coincidental. The code presented caries with it no guarantee
                                                       outside my statements that IT WORKED FOR ME.
                                                       
                                                       If, in the future this code is used in any products, please provide proper recognition for my
                                                       efforts.
                                                       
                                                       Jack Chaney, 2018 Chaney Firmware
                                                      ***************************************************************************************************/
                                                      #include "types.h"
                                                      
                                                      #define EICRA_INIT   (1<<ISC00)|(1<<ISC10) /* Trigger INT0 and INT1 on either edge */
                                                      #define EIMSK_INIT   (1<<INT0)|(1<<INT1)  /* Set INT0 and INT1 active */
                                                      
                                                      bool isCrkRising(void) { return false; } /* these are future calibration values, but for now force the false falling edge */
                                                      bool isCamRising(void) { return false; }
                                                      bool isInt0Low(void) { return ((PIND & (1<<PD2)) != 0); }
                                                      bool isInt1Low(void) { return ((PIND & (1<<PD3)) != 0); }
                                                      
                                                      SLong crkTime;
                                                      SLong crkDiff;
                                                      SLong camTIme;
                                                      SLong camDiff;
                                                      
                                                      UByte toothAfterCam;
                                                      
                                                      void initIgn(void) {
                                                       EICRA = EICRA_INIT;
                                                       EIMSK = EIMSK_INIT;
                                                       crkTime = camTime = 0;
                                                       toothAfterCam = 0;
                                                      }
                                                      void rtUpdateIgn(void) { /* not used yet, but a place holder */
                                                      }
                                                      /**************************************************************************************************
                                                       Crank interrupt
                                                       Interrupt is called on both rising and falling edges of crank signal.
                                                       - Active edge is defined as falling edge, or rising edge
                                                          Active edge is determined as falling and signal low, or rising and signal high.
                                                       - Primary activity for the interrupt is to determine angular velocity and cam angle
                                                         Using high speed clock timer, obtain current time as tempTime
                                                         and using previous time (crkTime) calculate a difference between active signals.
                                                       - Preserve time and difference as crkTime and crkDiff
                                                      **************************************************************************************************/
                                                      ISR(INT0_vect) {
                                                       SLong tmpTime = getTime1();
                                                       SLong tmpDiff = tmpTime + (tmpTime > crkTime ? 0 : 0x40000000) - crkTime;
                                                      /**************************************************************************************************
                                                       Because the timer has a limit of a max value before rollover, the 32 bit number is limited
                                                       to only using 30 bits before rollover. At rollover occurrence a factor is added to retain
                                                       the proper value for DIFF. TIME is unaffected.
                                                      **************************************************************************************************/
                                                       if (isInt0Low() ^ isCrkRising()) {
                                                      /**************************************************************************************************
                                                        use the cam signal to locate the tooth
                                                      **************************************************************************************************/
                                                        toothAfterCam++;
                                                        if (isCamDet()) {
                                                         setCamDet(false); /* cam detected so clear the flag */
                                                         toothAfterCam = 0;
                                                        }
                                                        crkTime = tmpTime;
                                                        crkDiff = tmpDiff;
                                                       }
                                                      }
                                                      /**************************************************************************************************
                                                       Cam interrupt
                                                      **************************************************************************************************/
                                                      ISR(INT1_vect) {
                                                       SLong tmpTime = getTime1();
                                                       SLong tmpDiff = tmpTime + (tmpTime > crkTime ? 0 : 0x40000000) - camTime;
                                                       if (isInt1Low() ^ isCamRising()) {
                                                        setCamDet(true); /* simple signal for now */
                                                        camTime = tmpTime;
                                                        camDiff = tmpDiff;
                                                       }
                                                      }
                                                      /* end of file */
                                                      
                                                      

                                                       

                                                      To update the main from before just have it make calls to:

                                                      initTimers(); and initIgn();

                                                       

                                                      there aren't any outputs yet so if you compile and load it on the Nano, you won't get anything visible happening.  The code is presented as a starting point. Read the comments and ask questions.  Each of the sections are going to be expanded.  The cam will add conditional mechanisms for MOPAR, and VVT decoding, and crank will have a missing or filled tooth detection part as well.

                                                       

                                                      If this looks familiar, it's because this part hasn't changed much from my stuff before.

                                                       

                                                      Enjoy,

                                                      Jack

                                                        • Re: Engine Management
                                                          jack.chaney56

                                                          I hate when I have a lapse in content. I was looking through the previous information and noticed a glaring problem... the code for the timers, is practically devoid of comments. I think it is because I was doing a bunch of cut and paste to provide the material in an incremental fashion (not making people drink from a fire hose). I will be sure to be mindful of not letting that happen again, and in the meantime...

                                                           

                                                          First the grouping of 0 and 2 are because the 328 has one 16 bit and two 8 bit timer counters. In some form of infinite wisdom, they made timer 0 an 8 bit, then timer 1 a 16 bit, but went back to an 8 bit for timer 2.  As a result, I made one of the 8 bit timers my 1mS real-time, timekeeper, so I could preserve the 16 bit counter to have special duties. To make the 8 bit work for a 1mS period, I slowed the clock so 250 tics would make for a 1mS elapsed time. Because the 16 bit clock has a wider counter space, I bumped the speed so it is able to have 2000 tics per mS.  This explains the settings for the TCCR registers. For now, I have two interrupts running, one is the timer 0 compare A, which is used for the 1mS timer. The second is the timer 1 overflow. The other timer interrupts will be called into service in a future installment

                                                           

                                                          There are also overflow interrupts, that enable a counter extension, providing the upper 16 bits of a 32 bit counter. For the 16 bit counter, the overflow adds 65536 or 216, and for the 8 bit counter, the overflow adds 256 or 28.

                                                           

                                                          Lastly, because of the speed difference, the slower 8 bit timer actually very closely synchronizes with the 16 bit by shifting the 16 bit right 3 bits. The other element is to limit the size of the counter value to 30 bits for the fast timer, and 27 bits for the slow timer.

                                                          1 of 1 people found this helpful
                                                          • Re: Engine Management
                                                            mp2100

                                                            I will admit I haven't (yet) read through in detail your posts, but, I do like your disclaimer in your code.  If I can remember next time I start a new project, I would like to copy that.  Or a reasonable facsimile.  If you don't object.

                                                             

                                                            1. All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                            2. another location or form is purely coincidental. The code presented caries with it no guarantee
                                                            3. outside my statements that IT WORKED FOR ME.
                                                              • Re: Engine Management
                                                                jack.chaney56

                                                                Hi Allen,

                                                                Sure... I figure as soon as I post stuff here, it is going to be scooped up and used by more than a few people. The important part is actually the next that asks to remember me when you become rich and famous.  I didn't put it in the header, but I usually add a history line.

                                                                 

                                                                History: Original Creation, as the earth was cooling.

                                                                 

                                                                Which is to say, most of this stuff is derived from methods I have been using for a really long time.

                                                                 

                                                                Might as well say it here, the code to this point, will make the Nano into a fully usable device with no non custom code. The Arduino interface is really nice for people learning how to use it, but really slows things down if you are trying to make things run as fast as possible.

                                                                 

                                                                Jack

                                                            • Re: Engine Management
                                                              jack.chaney56

                                                              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 auto-pilot. If anyone had not done this before, it is probably a good idea to go over how to accomplish it.

                                                               

                                                              From Eclipse, on the icon bar, there is a button that says AVR with an arrow. This is the "Upload current project to Atmel target MCU" button. Until you configure it, it will complain. First, highlight the project in the window to the left then on the menu bar select Project->Properties then AVR->AVRDude in the Programmer tab, Programmer configuration, click on Edit and select the programmer you are using from the list. The super cheep kind is probably USBasp down toward the bottom of the list. Remember to enter the Configuration name before you say OK, and ok your way back out.

                                                               

                                                              The AVRDude program looks for an AVR Release so go to Project->Build Configurations->Set Active and choose 2 Release. Then rebuild your project. All set...

                                                               

                                                              Plug the USB for the programmer into the workstation USB port, Plug the 6way programmer onto the Nano programmer pins, and plug the Nano USB into a power source, or the USB of the workstation. Then click on the AVR button on the icon bar and the process starts.  It takes a moment but will return with a status of the operation.

                                                               

                                                              Second, It is always good to have a communication channel to the outside world. The Nano conveniently has a USB serial port built in, and the programming of the Tx/Rx lines is actually quite simple (especially since I have done it on dozens of other systems). The workstation will need software also. I use PuTTY, because it is easy to get, easy to install, portable to other systems, and is free. PuTTY defaults to SSH, so it needs to be configured as a Serial, so click on the Serial radio button. Set the Serial line to the communication port that talks to the Nano (example: /dev/ttyUSB0). This can be found when you plug the Nano into the USB port, then read the device information (on Linux, just run dmesg and it will probably be at the bottom of the list). Then set the Speed to the baud rate in use (the source I am presenting is 19200). Last, Enter a name in the Saved Sessions box and click Save to preserve the settings. When you come back, just click on the entry in the Saved Sessions table.

                                                               

                                                              Now the source... This is a straight forward serial communication implementation. It uses input and output buffering, and runs both input and output as interrupt processes. The advantage is the program can dump the full string into the output buffer, and go back to work, not having to wait for each character to be sent. Similarly, it is not critical for the program to respond to each character and can fetch instructions as a whole when they are sent.

                                                              /***************************************************************************************************
                                                               All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                               another location or form is purely coincidental. The code presented caries with it no guarantee
                                                               outside my statements that IT WORKED FOR ME.
                                                               
                                                               If, in the future this code is used in any products, please provide proper recognition for my
                                                               efforts.
                                                               
                                                               Jack Chaney, 2018 Chaney Firmware
                                                              ***************************************************************************************************/
                                                              #include "types.h"
                                                              #define UBAUD_57_6    34
                                                              #define UBAUD_38_4    51
                                                              #define UBAUD_19_2    103
                                                              #define UBAUD__9_6    207
                                                              
                                                              #define BUFFSIZE      32
                                                              
                                                              #define TXI0          (1<<TXCIE0)
                                                              #define UCSRA_INIT    (1<<U2X0)                           /* Timebase for UART 2x clock */
                                                              #define UCSRB_INIT    (1<<TXEN0)|(1<<RXEN0)|(1<<RXCIE0)   /* Enable transmit and receive lines and receive interrupt */
                                                              #define UCSRC_INIT    (3<<UCSZ00)|(0<<UPM00)|(0<<UMSEL00) /* n-8-1 */
                                                              #define COMMSPEED     UBAUD_19_2                          /* 19200 */
                                                              
                                                              static UByte rxBuf[BUFFSIZE];
                                                              static UByte txBuf[BUFFSIZE];
                                                              static volatile UByte rxHed, rxTal, rxSiz;
                                                              static volatile UByte txHed, txTal, txSiz;
                                                              
                                                              bool isComReady(void) { return (rxSiz != 0); }
                                                              
                                                              void initUart(void) {
                                                               UCSR0A = UCSRA_INIT;
                                                               UCSR0B = UCSRB_INIT;
                                                               UCSR0C = UCSRC_INIT;
                                                               UBRR0 = COMMSPEED;
                                                               rxHed = 0; rxTal = 0; rxSiz = 0;
                                                               txHed = 0; txTal = 0; txSiz = 0;
                                                              }
                                                              /*==============================*/
                                                              /*       *** WARNING ***        */
                                                              /*  Do not use these functions  */
                                                              /*     within an interrupt      */
                                                              /*==============================*/
                                                              void putCom(UByte c) {
                                                               if ((UCSR0B & TXI0) == 0) {                              /* output buffer empty and no byte currently being sent */
                                                                UDR0 = c; UCSR0B |= TXI0;                               /* put byte in output port, and turn on the interrupt */
                                                               } else {
                                                                while (txSiz >= BUFFSIZE);                              /* make sure there is room in the buffer, or wait (see warning) */
                                                                cli();                                                  /* turn off interrupts, for safety */
                                                                txBuf[txTal] = c;                                       /* put the byte on the end of the buffer */
                                                                txTal = (txTal + 1) < BUFFSIZE ? txTal + 1 : 0;         /* adjust the pointer */
                                                                txSiz++;                                                /* increment the character counter */
                                                                sei();                                                  /* all done so turn the interrupts back on */
                                                               }
                                                              }
                                                              UByte getCom(void) {
                                                               UByte c = 0;
                                                               if (rxSiz > 0) {                                         /* if there isn't an input byte, just return 0 */
                                                                cli();                                                  /* turn off interrupts, for safety */
                                                                c = rxBuf[rxHed];                                       /* fetch the byte from the head of the buffer */
                                                                rxHed = (rxHed + 1) < BUFFSIZE ? rxHed + 1 : 0;         /* adjust the pointer */
                                                                --rxSiz;                                                /* decrement the character counter */
                                                                sei();                                                  /* all done so turn the interrupts back on */
                                                               }
                                                               return c;
                                                              }
                                                              /*==============================*/
                                                              ISR(USART_RX_vect) {                                      /* interestingly, the interrupt looks just like the runtime */
                                                               UByte c = UDR0;                                          /* fetch the byte from the input port */
                                                               if (rxSiz < BUFFSIZE) {                                  /* make sure there is room in the buffer, but because it is the interrupt, can't wait */
                                                                cli();                                                  /* block the other interrupts, for safety */
                                                                rxBuf[rxTal] = c;                                       /* put the byte on the end of the buffer */
                                                                rxTal = (rxTal + 1) < BUFFSIZE ? rxTal + 1 : 0;         /* adjust the pointer */
                                                                rxSiz++;                                                /* increment the character counter */
                                                                sei();                                                  /* all done so turn the interrupts back on */
                                                               } else { /* Dropped bytes go here */ }
                                                              }
                                                              ISR(USART_TX_vect) {                                      /* this could nearly be a cut and paste, but the buffer names are swapped */
                                                               UByte c;
                                                               if (txSiz > 0) {                                         /* special case for last one out */
                                                                cli();                                                  /* block the other interrupts, for safety */
                                                                c = txBuf[txHed];                                       /* fetch the byte from the head of the buffer */
                                                                txHed = (txHed + 1) < BUFFSIZE ? txHed + 1 : 0;         /* adjust the pointer */
                                                                --txSiz; UDR0 = c;                                      /* decrement the character counter, and put the byte in the output port */
                                                                sei();                                                  /* all done so turn the interrupts back on */
                                                               } else {
                                                                UCSR0B &= ~TXI0;                                        /* last byte out
                                                               }
                                                              }
                                                              /* end of file */
                                                              
                                                              

                                                              Same as before, add initUart() to the start up. The main() process can loop to listen for isComReady(), then getCom() and putCom(c); just like you would think.

                                                              2 of 2 people found this helpful
                                                              • Re: Engine Management
                                                                jack.chaney56

                                                                Two more modules that will provide interfaces to the rest of the hardware in the 328, are the A/Ds and the EEPROM.  First the A/D which is an 8 channel 10 bit device. It runs quite fast, but I wanted to have it operate invisibly in the background and not poll the line and wait for the conversions to complete, so it runs as an interrupt operation.

                                                                /***************************************************************************************************
                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee
                                                                 outside my statements that IT WORKED FOR ME.
                                                                 
                                                                 If, in the future this code is used in any products, please provide proper recognition for my
                                                                 efforts.
                                                                 
                                                                 Jack Chaney, 2018 Chaney Firmware
                                                                ***************************************************************************************************/
                                                                #include "types.h"
                                                                #define ADCSRA_INIT   (7<<ADPS0)     /* Right aligned Free running /64 prescaler */
                                                                #define A2D_CHAN   8                 /* number of A/D channels */
                                                                #define A2D_FILTER   8               /* 8 to 1 filter */
                                                                #define A2D_HALF   (A2D_FILTER/2)    /* used for rounding the result */
                                                                
                                                                static UByte a2dSemi4;               /* pseudo semaphore, are updates being done */
                                                                static SWord a2dVal[A2D_CHAN];
                                                                static UByte a2dChan;                /* current converting A/D */
                                                                
                                                                SWord getA2d(UByte v) { return (v < A2D_CHAN ? ((a2dVal[v] + A2D_HALF) / A2D_FILTER) : 0); }
                                                                
                                                                void initA2D(void) {
                                                                 a2dSemi4 = 0;                       /* pseudo semaphore, to only run the conversions when ready */
                                                                 for (a2dChan = 0; a2dChan < A2D_CHAN; a2dChan++) {
                                                                  a2dVal[a2dChan] = 0;               /* clear the buffer */
                                                                 }
                                                                 ADCSRA = ADCSRA_INIT;               /* configure the A/D */
                                                                }
                                                                void rtUpdateA2D(void) {
                                                                 if (a2dSemi4 == 0) {                /* don't run if semaphore is active(already running) */
                                                                  a2dSemi4 = 1;                      /* started, so set the semaphore on */
                                                                  a2dChan = 0;                       /* start with the first A/D */
                                                                  ADMUX = a2dChan;                   /* set the A/D channel */
                                                                  ADCSRA |= (1 << ADEN);             /* enable the A/D channel */
                                                                  ADCSRA |= (1 << ADSC);             /* start the A/D conversion */
                                                                  ADCSRA |= (1 << ADIE);             /* turn on the interrupt */
                                                                 }
                                                                }
                                                                ISR(ADC_vect) {
                                                                 SWord tmp = ADC;                    /* conversion complete, so fetch the A/D value */
                                                                 ADCSRA &= ~(1<<ADEN);               /* disable the A/D while changing the MUX */
                                                                /* the filtering is a running average... remove one part and add in the new as replacement */
                                                                 a2dVal[a2dChan] += tmp - (a2dVal[a2dChan] / A2D_FILTER);
                                                                 a2dChan++;                          /* point to the next channel */
                                                                 if (a2dChan < A2D_CHAN) {           /* check to see if all the channels are done */
                                                                  ADMUX = a2dChan;                   /* set the A/D channel */
                                                                  ADCSRA |= (1 << ADEN);             /* enable the A/D channel */
                                                                  ADCSRA |= (1 << ADSC);             /* start the A/D conversion */
                                                                 } else {
                                                                  ADCSRA &= ~(1 << ADIE);            /* all the channels read, so turn off the interrupt */
                                                                  a2dSemi4 = 0;                      /* and clear the interrupt */
                                                                 }
                                                                }
                                                                /* end of file */
                                                                
                                                                

                                                                 

                                                                The EEPROM code is nearly lifted verbatim from the Atmel documentation, so no real explanation.

                                                                /***************************************************************************************************
                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in
                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee
                                                                 outside my statements that IT WORKED FOR ME.
                                                                 
                                                                 If, in the future this code is used in any products, please provide proper recognition for my
                                                                 efforts.
                                                                 
                                                                 Jack Chaney, 2018 Chaney Firmware
                                                                ***************************************************************************************************/
                                                                #include "types.h"
                                                                
                                                                UByte getEEPROM(UWord ofs) {
                                                                 UByte rVal = 255;
                                                                 if (ofs <= E2END) {
                                                                  while(EECR & (1 << EEPE)); EEAR = ofs;
                                                                  EECR |= (1 << EERE); rVal = EEDR;
                                                                 }
                                                                 return rVal;
                                                                }
                                                                void putEEPROM(UWord ofs, UByte d) {
                                                                 if (ofs <= E2END) {
                                                                  while(EECR & (1 << EEPE)); EEAR = ofs;
                                                                  EEDR = d; EECR |= (1 << EEMPE); EECR |= (1 << EEPE);
                                                                 }
                                                                }
                                                                /* end of file */
                                                                
                                                                
                                                                
                                                                1 of 1 people found this helpful
                                                                • Re: Engine Management
                                                                  jack.chaney56

                                                                  Back again...

                                                                  The next installment is to discuss units of measure and how to look at things from the computer's point of view.  The first measurement and the one that will be used first in the example is measurement of angle. If you have taken lots of math classes and had Geometry, you would probably remember a lot of this stuff, but if you haven't had the classes, and only kind of understand how angles work, I will provide an explanation in a method that I hope will be easy to understand. The angle system is based on the points of a circle, and a line drawn from the perimeter of the circle to the center. If you draw two lines from the perimeter to the center, the two lines come together at an angle. Map makers established a measurement system for angles and declared the unit of measure to be degrees, and there were 360 degrees in a circle. Which is to say the measurement of degrees around the circle increases, until the points join. This is a measurement of 360 degrees.

                                                                   

                                                                  Math moves forward, and a need for measuring the distance around the circle was needed, so a ratio between the distance from the perimeter to the center is called the radius, and the distance around the perimeter is called the circumference. Long ago it was proven that no matter what the radius of the circle was, the ratio between radius and circumference was a constant and circumference. That constant is named pi where circumference = 2 * pi * radius. Lots of work to determine the exact value of pi has been attempted over the years, and memorization of digits of pi has been a point of contest for nearly as long. Which led to another unit of angular measure called radians where 360 degrees equated to 2pi.

                                                                   

                                                                  With that background, I can now bring in my update. A circle having 2pi radians, and pi being a constant number, and because a significant amount of derivation of geometric formulas have been performed, if pi is thought of as just a constant, it is possible to use a binary constant, and if a conversion is provided, all the math will remain. My goal was to use a numbering system that worked well for computers, which operate in binary, and to avoid using floating point to maintain processing speed. I settled on using a 16 bit integer as a base value with a sign, and a signed integer limits to +/- 215. The result then would make the value of 2pi = 32678, so pi would equate to 16384, and when necessary, a constant conversion is possible to invoke. In the meantime, much of the world, and most of the people working with engine management, work with degrees, and if 360 degrees equates to 32768, the measurement of angle is +/- 0.0109 degrees, which is a decent tolerance. There is a secondary advantage for using this method, which is, in a four cycle system, because the cam cycle is two rotations, and it is necessary to know the position in the cam cycle, by using an unsigned 16 bit value, the calculations become greatly simplified.

                                                                   

                                                                  With all that explained, I will add one more element to the process. Because in fixed point (integer) operation, you don't want your number to become too big or too small, I am introducing a PART1 and PART2 values. These numbers when multiplied equate to the total degrees in the circle (DEG_PER_REV) which from previous is 32768. PART1 and PART2 are also selected as binary constants, so multiplication or division is performed as a simple left or right bit shift, and in source code, the simple addition of:

                                                                   

                                                                  #define PART1    64L        /* part 1 of full circle for calculations */
                                                                  #define PART2    512L       /* part 2 of full circle for calculations */
                                                                  #define DEGS_PER_REV  (PART1*PART2)
                                                                  
                                                                  

                                                                   

                                                                  Since this is used in lots of places, a common include file will contain these lines.

                                                                   

                                                                  I wanted to get a realistic measurement for the existing code, so I thought RPM would be a good value to provide, and it is possible to calculate with the given values to this point. The values needed are the time difference between teeth (crkDiff, measured in tics), the number of tics per minute (TICS_PER_mS * mS_PER_SEC * SEC_PER_MIN), which is a constant, and the number of teeth in a revolution (teeth, read from calibration). The equation becomes:

                                                                   

                                                                  RPM = TICS_PER_MIN / (teeth * crkDiff);

                                                                   

                                                                  Because TICS_PER_MIN is constant, and the value of teeth, doesn't change during runtime, it is possible to calculate

                                                                   

                                                                  preRpm = TICS_PER_MIN / teeth;

                                                                   

                                                                  as a saved constant when the engine is not running and the runtime equation is a single divide

                                                                   

                                                                  RPM = preRpm / crkDiff;

                                                                   

                                                                  Tying it all together...

                                                                  In the code previously, the ignition management had a "void rtUpdateIgn(void)" function, with a comment of "not used yet, but a place holder".  It's time to implement code here. To enable the code, just add a call the function in the 1mS timer0 interrupt. The function will be divided into two parts, one part for functions while the engine is stopped, and the other part for while the engine is running.  To implement this capability, a timeout value is maintained. The value is updated at each tooth interrupt, and decremented on each pass of the loop.  When the value reads zero, the engine is stopped.  The updated section for the first update is:

                                                                   

                                                                  #define TICS_PER_mS     2000
                                                                  #define mS_PER_SEC      1000
                                                                  #define SEC_PER_MIN     60
                                                                  #define TICS_PER_MIN    (TICS_PER_mS*mS_PER_SEC*SEC_PER_MIN)
                                                                  
                                                                  SWord teeth;
                                                                  SWord getCrankToothCt(void) {
                                                                  /* Calibration value in future, but for now just provide a constant */
                                                                       return 4;
                                                                  }
                                                                  SWord rpm;
                                                                  SWord preRpm;
                                                                  
                                                                  void rtUpdateIgn(void) {
                                                                   if (tmOut > 0) {
                                                                    --tmOut;
                                                                    rpm = preRpm / crkDiff;
                                                                   } else {
                                                                    teeth = getCrankToothCt();
                                                                    preRpm = TICS_PER_MIN / teeth;
                                                                   }
                                                                  }
                                                                  

                                                                   

                                                                  It might be necessary for the preRpm value to be an unsigned long, because of the size, but otherwise, this should work. The other part is the update for tmOut that is performed in the crank interrupt.

                                                                   

                                                                  ...
                                                                  #define QUART_SEC     250     /* provide a 250 mS timeout 1/4 second */
                                                                  ...
                                                                  ISR(INT0_vect) {
                                                                  ...
                                                                   if (isInt0Low() ^ isCrkRising()) {
                                                                    tmOut = QUART_SEC;
                                                                  ...

                                                                   

                                                                  This is a lot for an installment, but I did sort of want to have more. I will pick up to get the next component soon.

                                                                   

                                                                  Jack

                                                                  1 of 1 people found this helpful
                                                                    • Re: Engine Management
                                                                      jack.chaney56

                                                                      ...good, I went through the discussion of angular measurement and what is done to preserve relevant values, then didn't use what I had presented. Instead, I went to time and RPM which doesn't use it. However, the information is important, and will be needed for the next update, where I start to discuss timing and scheduler operation.

                                                                       

                                                                      Jack

                                                                    • Re: Engine Management
                                                                      jack.chaney56

                                                                      Conclude the discussion about angles.

                                                                      For operation of an engine, the measurements are all done as angles. Angle of advancement, angle of dwell, and top dead center reference. Switching over a little to engine operation theory... an engine running as a four cycle motor, each cylinder has four points of reference, two are at the upper limit of the stroke, and two at the lower limit of the stroke. The four cycles are the transitions between each of the four states, 1 compression/ignition, 2 power, 3 exhaust, and 4 fueling. The position when the piston is at the upper limit on the compression cycle is called its top dead center. For reference in an engine system, cylinder 1 is the reference, and its top dead center (TDC) is considered zero degrees. Because each cylinder has four cycles, return to TDC occurs every 720 degrees. Also, because each cylinder has four cycles, and operations are tied  to which cycle is in operation, the two rotations is referred to as a cam cycle, because the cam shaft is the device that controls the opening and closing of the valves at the proper time (in mechanical systems).

                                                                       

                                                                      Each of the pistons is spaced out to provide an ignition at equally spaced locations along the 720 degree cam rotation. This means each cylinder will have a referencing TDC angle. The angle is dependent on the number of cylinders. For a 4 cylinder motor, the TDCs will be at 0 degrees, 180 degrees, 360 degrees, and 540 degrees. Likewise, for a 6 cylinder, the TDCs will be at 0 degrees, 120 degrees, 240 degrees, 360 degrees, 480 degrees, and 600 degrees.  This will be important in just a little bit.

                                                                       

                                                                      Remembering from before, the angular measurement is 32768 degrees per rotation. so the conversion is angle * 32768 / 360. Many of the angles have direct conversions with no remainder portion (truncated values), but some do. It is important to have update values for the crank teeth. For some tooth configurations, like the common 4x the tooth angle is easily divisible to a whole number, however many advance wheels like 24x or 60-2 do not divide into convenient whole numbers. a quick mechanism for providing angular location. The method used is a carry over from the same one used to draw straight lines on pixel based displays. The method uses an error and correction operation to provide a running update of the angle to maintain a "close as possible" angle. The process works like this...

                                                                       

                                                                      A value of degrees per tooth is calculated as degrees per rev divided by teeth per crank, which provides a truncated integer division. The error correction value is the remainder after division or mod. The equations then are:

                                                                       

                                                                      toothAngle = DEG_PER_REV / teeth;

                                                                      toothError = DEG_PER_REV % teeth;

                                                                       

                                                                      The values are based on the number of teeth and are constant during runtime. This means, they are calculated in the engine off time, and don't impact the operation timing of engine running. The procedure works in a rather simple way. At each tooth interrupt, a value of cam angle is incremented by the value toothAngle, and at the same time, a cam error is incremented by toothError. When the value of cam error is greater than or equal to the value of teeth, the threshold is reached, and the cam angle is incremented by 1 and the cam error is corrected by subtracting the value of teeth. This provides a running value of the angle of the engine at the most recent tooth interrupt.

                                                                       

                                                                      The result is having the timer tic value of the most recent tooth interrupt (crkTime), the angle in the cam rotation of the most recent tooth interrupt (camAngle), and the predicted time until the next tooth interrupt event (crkDiff). With this information, it is possible to perform conversions between time and angle, resulting in creating predicted event times for setting timer interrupts. Because the conversions require math (multiplies and divides) also for providing values of proper precision, are performed using 32bit numbers, it is necessary to limit the amount of repetition and unnecessary conversion.

                                                                       

                                                                      The solution presented here is to use a window of operation. The window will exist based on the most current values of crkTime, crkDiff, and camAngle. Because an operation is needed, the window should be forward of the current position (>camAngle). The system here uses a value of 1/2 the tooth angle. This is the value that is used for "min", or the front edge of the window. The value of "max" will be one tooth angle after "min" with the idea being, from mid tooth to mid tooth. The basis of the operation is to perform an angle based comparison of the event angle to the min and max value. If the event angle falls between the min and max value, then the angle is converted to a time and the timers are updated to generate an event.

                                                                       

                                                                      In this installment, I will just provide snippets of code to add as correction to existing source. In the next session, I will provide the full file sources.

                                                                       

                                                                      /* in rtUpdateIgn() in the engine off section */
                                                                      ...
                                                                      toothAngle = DEG_PER_REV / teeth;
                                                                      toothError = DEG_PER_REV % teeth;
                                                                      
                                                                      
                                                                      
                                                                      /* in ISR(INT0_vect) after the if (isInt0Low... */
                                                                        camAngle += toothAngle;
                                                                        camError += toothError; if (toothError >= teeth) { camAngle++; camError -= teeth; }
                                                                      ...
                                                                      /* this doesn't provide correction for partial error correction */
                                                                        min = camAngle + (toothAngle / 2);
                                                                        max = min + toothAngle;
                                                                      ...
                                                                      /* this is actually a good place to invoke the scheduler, as long as it isn't */
                                                                      /* called to do too much in its operation. */

                                                                       

                                                                      The basis of the scheduler does a simple operation based on min and max

                                                                      /* because min and max are unsigned values making provision for wrap around is done */
                                                                      /* as duplication of similar operation (just easier than making things complex) */
                                                                      if (min < max)
                                                                           if ((min < eventAngle) && (eventAngle <= max)) { /* convert and update single event timers */ }
                                                                      } else {
                                                                           if ((min < eventAngle) || (eventAngle <= max)) { /* convert and update single event timers */ }
                                                                      }
                                                                      1 of 1 people found this helpful
                                                                        • Re: Engine Management
                                                                          jack.chaney56

                                                                          I did want to add one last item here. In the min / max operation (the scheduler), there is a reference to the "convert and update single event timers", but I did not provide the operation. This is the primary reason for splitting the total degrees of the circle into two component parts (PART1 and PART2). The reason is it is possible to use a calculated value to eliminate much of the operation.  The way it works is to use a base value, where:

                                                                           

                                                                          base = crkDiff * teeth / PART1;

                                                                           

                                                                          Then if you are converting from time to angle the operation is:

                                                                           

                                                                          angle = time * PART2 / base;

                                                                           

                                                                          ...and converting from angle to time is:

                                                                           

                                                                          time = angle * base / PART2;

                                                                           

                                                                          The time at the scheduler will be the number of tics from the last tooth. So to set the event timer compare value will be set to (crkTime + time), where time is the converted value.

                                                                           

                                                                          Jack

                                                                        • Re: Engine Management
                                                                          jack.chaney56

                                                                          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

                                                                          1 of 1 people found this helpful
                                                                            • Re: Engine Management
                                                                              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

                                                                            • Re: Engine Management
                                                                              jack.chaney56

                                                                              Presenting two tricks.

                                                                               

                                                                              I hesitate to call them tricks, because someone once said they weren't interested in tricks that nobody else would know about. The truthful statement is a derived technique. The result of years of experience, and effort, providing a knowledge to distill a procedure to its most effective form. A clever trick. The first is from necessity, because to have both ignition and fueling, two pair of timer compare registers were needed. However, in the 328, only one timer set is 16 bit, the other set is only 8 bits. To compensate, the 8 bit timer is slowed so 250 tics equals 1mS.

                                                                               

                                                                              The difference between the two is the 16 bit timer runs 8 times faster than the 8 bit timer. Important information to note. All the equations for time will remain the same, and the only change is, for slow timers, the elapseTime and eventTime get shifted right by 3 (divide by 8). Part one solved, now there could be a problem with the limit of just a little over 1mS for the compare register. The problem isn't there with the 16 bit register which is big enough for about 32mS, meaning another technique would be necessary for extended times, and anything greater than 1mS.  I elected to just use the overflow interrupt to manage the countdown, and activate the timer compare when the overflow was a match to the upper portion of the effective time value. Once again using the tach value, but changing to use timer 2 compare registers. For the operation, everything stays the same in the scheduler portion. The change is in the ignTkHi and ignTkLo functions. Instead of checking to see if there is a long delay, it just uses the extended time value. The only math is to do the right shift three bits. There is also an active bit, otherwise it would require disabling the overflow counter which would put it out of sync with the tooth timer.

                                                                               

                                                                              I said I was going to provide full listings in the next installment, but I want to do a bunch of clean up first, so it will have to wait a bit longer.  The code for the slow timer goes like this. First change the ignTkHi and ignTkLo

                                                                              /**************************************************************************************************
                                                                              Generated TACH signal
                                                                              **************************************************************************************************/
                                                                              void ignTkHi(SLong et, SLong bt) {
                                                                                  SLong ev = (et + bt + 4) >> 3;
                                                                              
                                                                                  etTkHi = (UByte)(ev & 0xff);
                                                                                  dlyTkHi = ev & 0x07ffff00;
                                                                                  tkHi = 1;
                                                                              }
                                                                              void ignTkLo(SLong et, SLong bt) {
                                                                                  SLong ev = (et + bt + 4) >> 3;
                                                                              
                                                                                  etTkLo = (UByte)(ev & 0xff);
                                                                                  dlyTkLo = ev & 0x07ffff00;
                                                                                  tkLo = 1;
                                                                              }
                                                                              
                                                                              
                                                                              

                                                                               

                                                                              Then the timer overflow and compare

                                                                              ISR(TIMER2_COMPA_vect) { TIMSK2 &= ~(1 << OCIE2A); setTach(true); }
                                                                              ISR(TIMER2_COMPB_vect) { TIMSK2 &= ~(1 << OCIE2B); setTach(false); }
                                                                              ISR(TIMER2_OVF_vect) {
                                                                                  ov2Tic = (ov2Tic +   256L) & 0x07ffff00;
                                                                                  if ((ov2Tic == dlyTkHi) && (tkHi == 1)) { /* timer match and flag on */
                                                                                    tkHi = 0;                               /* condition met, so turn off flag */
                                                                                    if (etTkHi > 0) {                       /* special condition if low order portion is 0 */
                                                                                      OCR2A = etTkHi; TIMSK2 |= (1 << OCIE2A); /* everything regular, so just like the 16 bit */
                                                                                    } else {                                /* if short portion is 0 the event is now */
                                                                                      setTach(true);                        /* tach on */
                                                                                    }
                                                                                  }
                                                                                  if ((ov2Tic == dlyTkLo) && (tkLo == 1)) { /* timer match and flag on */
                                                                                    tkLo = 0;                               /* condition met, so turn off flag */
                                                                                    if (etTkLo > 0) {                       /* everything else the same */
                                                                                      OCR2B = etTkHi; TIMSK2 |= (1 << OCIE2B);
                                                                                    } else {
                                                                                      setTach(false);                      /* tach on */
                                                                                    }
                                                                                  }
                                                                              } 
                                                                              
                                                                              

                                                                               

                                                                              Really, I will provide full source, but the day is getting long, and I wanted to send out the tricks.

                                                                               

                                                                              The other trick is with hardware. Because the 328 is limited in the number of output lines available, I used a 74ls138 (3 to 8 decoder), tied to the Nano D4-D6 pins, then tied D7 to the enable pin to act as a switch. I routed the output line to an inverter (74ls04), then to the gate of a quad latch (74ls75). There is enough inverters in one chip to enable 6 of the quad latches. I then tied D8-D11 to the inputs of the latches. With a bit of simple code to drive the whole thing, 8 output lines becomes 24. Which is enough for 8 coils, 8 injectors, and a handful of idiot lights. All for pocket change.  The code for the latching operation is:

                                                                              /***************************************************************************************************
                                                                              
                                                                               History: [JAC] Original creation when the earth was cooling
                                                                              ***************************************************************************************************/
                                                                              #include "types.h"
                                                                              
                                                                              #define MAX_LATCH            32
                                                                              
                                                                              static const UByte lchMask[] = {
                                                                                   0x01,0x02,0x04,0x08,0x11,0x12,0x14,0x18,0x21,0x22,0x24,0x28,0x31,0x32,0x34,0x38
                                                                                  ,0x41,0x42,0x44,0x48,0x51,0x52,0x54,0x58,0x61,0x62,0x64,0x68,0x71,0x72,0x74,0x78
                                                                              };
                                                                              static UByte lch[8];
                                                                              void initLatches(void) {
                                                                                  UByte c;
                                                                                  DDRB &= 0xf0; DDRD &= 0x0f;
                                                                                  for (c = 0; c < 8; c++) { lch[c] = 0; PORTB = 0; PORTD = c; PORTD = (PIND | 0x80); }
                                                                              }
                                                                              void setLatch(UByte n, bool t) {
                                                                                  UByte a, b, c;
                                                                              
                                                                                  if (n < MAX_LATCH) {
                                                                                      b = lchMask[n] & 0x0f; a = lchMask[n] & 0x70; c = n / 4;
                                                                                      lch[c] = t ? (lch[c] | b) : (lch[c] & ~b); PORTB = lch[c];
                                                                                      PORTD = (PIND & 0x0f) | a; PORTD = (PIND | 0x80);
                                                                                  }
                                                                              }
                                                                              bool isLatchOn(UByte n) {
                                                                                  return (n < MAX_LATCH ? ((lch[n>>2] & (lchMask[n] & 0x0f)) != 0) : false);
                                                                              }
                                                                              /* end of file */
                                                                              
                                                                              
                                                                              
                                                                              
                                                                              
                                                                              
                                                                              
                                                                              

                                                                              Thats it for now

                                                                              Jack

                                                                              1 of 1 people found this helpful
                                                                              • Re: Engine Management
                                                                                jack.chaney56

                                                                                Big one today as promised...

                                                                                 

                                                                                First is types.h which hasn't changed particularly from the first. Just added some #define stuff at the end. There should be a device specific general .h file (usually the project name)

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                  
                                                                                 This is the base include file, that pulls in all the AVR components, and standard items to make 
                                                                                 life easier. I also declare some items for variable naming that makes it easier for me 
                                                                                 to run on auto pilot.
                                                                                 
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #ifndef _TYPES_H  
                                                                                #define _TYPES_H  
                                                                                #include <stdbool.h>  
                                                                                #include <stdio.h>  
                                                                                #include <avr/interrupt.h>  
                                                                                #include <avr/io.h>  
                                                                                  
                                                                                typedef unsigned char  UByte;  
                                                                                typedef char           SByte;  
                                                                                typedef unsigned short UWord;  
                                                                                typedef short          SWord;  
                                                                                typedef unsigned long  ULong;  
                                                                                typedef long           SLong;  
                                                                                  
                                                                                /* Hardware interfacers */  
                                                                                #define REG_B(a)      *((volatile UByte* const)(a))  
                                                                                #define REG_W(a)      *((volatile UWord* const)(a))  
                                                                                #define REG_L(a)      *((volatile ULong* const)(a))  
                                                                                #define REG_Bs(a)     *((volatile SByte* const)(a))  
                                                                                #define REG_Ws(a)     *((volatile SWord* const)(a))  
                                                                                #define REG_Ls(a)     *((volatile SLong* const)(a))  
                                                                                #define ARR_B(a)      ((UByte* const)(a))  
                                                                                #define ARR_W(a)      ((UWord* const)(a))  
                                                                                #define ARR_L(a)      ((ULong* const)(a))  
                                                                                #define ARR_Bs(a)     ((SByte* const)(a))  
                                                                                #define ARR_Ws(a)     ((SWord* const)(a))  
                                                                                #define ARR_Ls(a)     ((SLong* const)(a))  
                                                                                  
                                                                                /* some use while(1) but I found this way compiles to fewer instructions */  
                                                                                #ifdef forever  
                                                                                #undef forever  
                                                                                #endif  
                                                                                #define forever    for(;;)  
                                                                                  
                                                                                /* handy bits */  
                                                                                #define bit0    1  
                                                                                #define bit1    2  
                                                                                #define bit2    4  
                                                                                #define bit3    8  
                                                                                #define bit4    16  
                                                                                #define bit5    32  
                                                                                #define bit6    64  
                                                                                #define bit7    128  
                                                                                #define bit8    256  
                                                                                #define bit9    512  
                                                                                #define bit10    1024  
                                                                                #define bit11    2048  
                                                                                #define bit12    4096  
                                                                                #define bit13    8192  
                                                                                #define bit14    16384  
                                                                                #define bit15    32768  
                                                                                  
                                                                                #define PART1    64L        /* part 1 of full circle for calculations */  
                                                                                #define PART2    512L       /* part 2 of full circle for calculations */  
                                                                                #define DEGS_PER_REV  (PART1*PART2)  
                                                                                #define TICS_PER_mS     2000  
                                                                                #define mS_PER_SEC      1000  
                                                                                #define SEC_PER_MIN     60  
                                                                                #define TICS_PER_MIN    (TICS_PER_mS*mS_PER_SEC*SEC_PER_MIN)  
                                                                                
                                                                                
                                                                                extern UWord etTkHi, etTkLo;  
                                                                                extern SWord dlyTkHi, dlyTkLo;  
                                                                                
                                                                                
                                                                                #endif  
                                                                                /* end of file */  
                                                                                

                                                                                 

                                                                                The second group are the files related to the hardware A2D.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                #define ADCSRA_INIT   (7<<ADPS0)     /* Right aligned Free running /64 prescaler */  
                                                                                #define A2D_CHAN   8                 /* number of A/D channels */  
                                                                                #define A2D_FILTER   8               /* 8 to 1 filter */  
                                                                                #define A2D_HALF   (A2D_FILTER/2)    /* used for rounding the result */  
                                                                                  
                                                                                static UByte a2dSemi4;               /* pseudo semaphore, are updates being done */  
                                                                                static SWord a2dVal[A2D_CHAN];  
                                                                                static UByte a2dChan;                /* current converting A/D */  
                                                                                  
                                                                                SWord getA2d(UByte v) { return (v < A2D_CHAN ? ((a2dVal[v] + A2D_HALF) / A2D_FILTER) : 0); }  
                                                                                  
                                                                                void initA2D(void) {  
                                                                                 a2dSemi4 = 0;                       /* pseudo semaphore, to only run the conversions when ready */  
                                                                                 for (a2dChan = 0; a2dChan < A2D_CHAN; a2dChan++) {  
                                                                                  a2dVal[a2dChan] = 0;               /* clear the buffer */  
                                                                                 }  
                                                                                 ADCSRA = ADCSRA_INIT;               /* configure the A/D */  
                                                                                }  
                                                                                void rtUpdateA2D(void) {  
                                                                                 if (a2dSemi4 == 0) {                /* don't run if semaphore is active(already running) */  
                                                                                  a2dSemi4 = 1;                      /* started, so set the semaphore on */  
                                                                                  a2dChan = 0;                       /* start with the first A/D */  
                                                                                  ADMUX = a2dChan;                   /* set the A/D channel */  
                                                                                  ADCSRA |= (1 << ADEN);             /* enable the A/D channel */  
                                                                                  ADCSRA |= (1 << ADSC);             /* start the A/D conversion */  
                                                                                  ADCSRA |= (1 << ADIE);             /* turn on the interrupt */  
                                                                                 }  
                                                                                }  
                                                                                ISR(ADC_vect) {  
                                                                                 SWord tmp = ADC;                    /* conversion complete, so fetch the A/D value */  
                                                                                 ADCSRA &= ~(1<<ADEN);               /* disable the A/D while changing the MUX */  
                                                                                /* the filtering is a running average... remove one part and add in the new as replacement */  
                                                                                 a2dVal[a2dChan] += tmp - (a2dVal[a2dChan] / A2D_FILTER);  
                                                                                 a2dChan++;                          /* point to the next channel */  
                                                                                 if (a2dChan < A2D_CHAN) {           /* check to see if all the channels are done */  
                                                                                  ADMUX = a2dChan;                   /* set the A/D channel */  
                                                                                  ADCSRA |= (1 << ADEN);             /* enable the A/D channel */  
                                                                                  ADCSRA |= (1 << ADSC);             /* start the A/D conversion */  
                                                                                 } else {  
                                                                                  ADCSRA &= ~(1 << ADIE);            /* all the channels read, so turn off the interrupt */  
                                                                                  a2dSemi4 = 0;                      /* and clear the interrupt */  
                                                                                 }  
                                                                                }  
                                                                                /* end of file */ 
                                                                                

                                                                                 

                                                                                UART.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                #define UBAUD_57_6    34  
                                                                                #define UBAUD_38_4    51  
                                                                                #define UBAUD_19_2    103  
                                                                                #define UBAUD__9_6    207  
                                                                                  
                                                                                #define BUFFSIZE      32  
                                                                                  
                                                                                #define TXI0          (1<<TXCIE0)  
                                                                                #define UCSRA_INIT    (1<<U2X0)                           /* Timebase for UART 2x clock */  
                                                                                #define UCSRB_INIT    (1<<TXEN0)|(1<<RXEN0)|(1<<RXCIE0)   /* Enable transmit and receive lines and receive interrupt */  
                                                                                #define UCSRC_INIT    (3<<UCSZ00)|(0<<UPM00)|(0<<UMSEL00) /* n-8-1 */  
                                                                                #define COMMSPEED     UBAUD_19_2                          /* 19200 */  
                                                                                  
                                                                                static UByte rxBuf[BUFFSIZE];  
                                                                                static UByte txBuf[BUFFSIZE];  
                                                                                static volatile UByte rxHed, rxTal, rxSiz;  
                                                                                static volatile UByte txHed, txTal, txSiz;  
                                                                                  
                                                                                bool isComReady(void) { return (rxSiz != 0); }  
                                                                                  
                                                                                void initUart(void) {  
                                                                                 UCSR0A = UCSRA_INIT;  
                                                                                 UCSR0B = UCSRB_INIT;  
                                                                                 UCSR0C = UCSRC_INIT;  
                                                                                 UBRR0 = COMMSPEED;  
                                                                                 rxHed = 0; rxTal = 0; rxSiz = 0;  
                                                                                 txHed = 0; txTal = 0; txSiz = 0;  
                                                                                }  
                                                                                /*==============================*/  
                                                                                /*       *** WARNING ***        */  
                                                                                /*  Do not use these functions  */  
                                                                                /*     within an interrupt      */  
                                                                                /*==============================*/  
                                                                                void putCom(UByte c) {  
                                                                                 if ((UCSR0B & TXI0) == 0) {                              /* output buffer empty and no byte currently being sent */  
                                                                                  UDR0 = c; UCSR0B |= TXI0;                               /* put byte in output port, and turn on the interrupt */  
                                                                                 } else {  
                                                                                  while (txSiz >= BUFFSIZE);                              /* make sure there is room in the buffer, or wait (see warning) */  
                                                                                  cli();                                                  /* turn off interrupts, for safety */  
                                                                                  txBuf[txTal] = c;                                       /* put the byte on the end of the buffer */  
                                                                                  txTal = (txTal + 1) < BUFFSIZE ? txTal + 1 : 0;         /* adjust the pointer */  
                                                                                  txSiz++;                                                /* increment the character counter */  
                                                                                  sei();                                                  /* all done so turn the interrupts back on */  
                                                                                 }  
                                                                                }  
                                                                                UByte getCom(void) {  
                                                                                 UByte c = 0;  
                                                                                 if (rxSiz > 0) {                                         /* if there isn't an input byte, just return 0 */  
                                                                                  cli();                                                  /* turn off interrupts, for safety */  
                                                                                  c = rxBuf[rxHed];                                       /* fetch the byte from the head of the buffer */  
                                                                                  rxHed = (rxHed + 1) < BUFFSIZE ? rxHed + 1 : 0;         /* adjust the pointer */  
                                                                                  --rxSiz;                                                /* decrement the character counter */  
                                                                                  sei();                                                  /* all done so turn the interrupts back on */  
                                                                                 }  
                                                                                 return c;  
                                                                                }  
                                                                                /*==============================*/  
                                                                                ISR(USART_RX_vect) {                                      /* interestingly, the interrupt looks just like the runtime */  
                                                                                 UByte c = UDR0;                                          /* fetch the byte from the input port */  
                                                                                 if (rxSiz < BUFFSIZE) {                                  /* make sure there is room in the buffer, but because it is the interrupt, can't wait */  
                                                                                  cli();                                                  /* block the other interrupts, for safety */  
                                                                                  rxBuf[rxTal] = c;                                       /* put the byte on the end of the buffer */  
                                                                                  rxTal = (rxTal + 1) < BUFFSIZE ? rxTal + 1 : 0;         /* adjust the pointer */  
                                                                                  rxSiz++;                                                /* increment the character counter */  
                                                                                  sei();                                                  /* all done so turn the interrupts back on */  
                                                                                 } else { /* Dropped bytes go here */ }  
                                                                                }  
                                                                                ISR(USART_TX_vect) {                                      /* this could nearly be a cut and paste, but the buffer names are swapped */  
                                                                                 UByte c;  
                                                                                 if (txSiz > 0) {                                         /* special case for last one out */  
                                                                                  cli();                                                  /* block the other interrupts, for safety */  
                                                                                  c = txBuf[txHed];                                       /* fetch the byte from the head of the buffer */  
                                                                                  txHed = (txHed + 1) < BUFFSIZE ? txHed + 1 : 0;         /* adjust the pointer */  
                                                                                  --txSiz; UDR0 = c;                                      /* decrement the character counter, and put the byte in the output port */  
                                                                                  sei();                                                  /* all done so turn the interrupts back on */  
                                                                                 } else {  
                                                                                  UCSR0B &= ~TXI0;                                        /* last byte out, disable the interrupt */
                                                                                 } 
                                                                                } 
                                                                                /* end of file */ 
                                                                                

                                                                                 

                                                                                EEPROM.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                  
                                                                                UByte getEEPROM(UWord ofs) {  
                                                                                 UByte rVal = 255;  
                                                                                 if (ofs <= E2END) {  
                                                                                  while(EECR & (1 << EEPE)); EEAR = ofs;  
                                                                                  EECR |= (1 << EERE); rVal = EEDR;  
                                                                                 }  
                                                                                 return rVal;  
                                                                                }  
                                                                                void putEEPROM(UWord ofs, UByte d) {  
                                                                                 if (ofs <= E2END) {  
                                                                                  while(EECR & (1 << EEPE)); EEAR = ofs;  
                                                                                  EEDR = d; EECR |= (1 << EEMPE); EECR |= (1 << EEPE);  
                                                                                 }  
                                                                                }  
                                                                                /* end of file */ 
                                                                                

                                                                                 

                                                                                and the file that bridges the gap TIMERS.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                 
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                  
                                                                                #define TIMSK0_INIT  (1<<OCIE0A)  
                                                                                #define TIMSK2_INIT  0  
                                                                                #define TIMSK1_INIT  (1<<TOIE1)  
                                                                                #define TCCR0A_INIT  0  
                                                                                #define TCCR0B_INIT  (3<<CS00)  
                                                                                #define TCCR2A_INIT  0  
                                                                                #define TCCR2B_INIT  (3<<CS00)  
                                                                                #define TCCR1A_INIT  0  
                                                                                #define TCCR1B_INIT  (2<<CS10)  
                                                                                #define TCCR1C_INIT  0  
                                                                                  
                                                                                #define mS_UPDATE_8  250  
                                                                                void setTach(bool);
                                                                                  
                                                                                SLong ov0Tic;  
                                                                                SLong ov2Tic;  
                                                                                SLong ov1Tic;  
                                                                                SLong getTime1(void) { ULong rVal; cli(); rVal = (ov1Tic | TCNT1); sei(); return rVal; }  
                                                                                  
                                                                                void initTimers(void) {  
                                                                                 TCCR0A = TCCR0A_INIT; TCCR0B = TCCR0B_INIT; TIMSK0 = TIMSK0_INIT;  
                                                                                 TCCR2A = TCCR2A_INIT; TCCR2B = TCCR2B_INIT; TIMSK2 = TIMSK2_INIT;  
                                                                                 TCCR1A = TCCR1A_INIT; TCCR1B = TCCR1B_INIT; TCCR1C = TCCR1C_INIT; TIMSK1 = TIMSK1_INIT;  
                                                                                }  
                                                                                ISR(TIMER0_COMPA_vect) {  
                                                                                  OCR0A += mS_UPDATE_8;  /* refresh for 1mS Timer Tic */  
                                                                                  if (dlyTkHi > 0) { --dlyTkHi; if (dlyTkHi == 0) { OCR1A = etTkHi; TIMSK1 |= (1 << OCIE1A); } }  
                                                                                  if (dlyTkLo > 0) { --dlyTkLo; if (dlyTkLo == 0) { OCR1B = etTkLo; TIMSK1 |= (1 << OCIE1B); } }  
                                                                                }  
                                                                                ISR(TIMER0_OVF_vect) { ov0Tic = (ov0Tic +   256L) & 0x07ffff00; } 
                                                                                 
                                                                                ISR(TIMER2_COMPA_vect) { TIMSK2 &= ~(1 << OCIE2A); setTach(true); }  
                                                                                ISR(TIMER2_COMPB_vect) { TIMSK2 &= ~(1 << OCIE2B); setTach(false); }  
                                                                                ISR(TIMER2_OVF_vect) {  
                                                                                    ov2Tic = (ov2Tic +   256L) & 0x07ffff00;  
                                                                                    if ((ov2Tic == dlyTkHi) && (tkHi == 1)) { /* timer match and flag on */  
                                                                                      tkHi = 0;                               /* condition met, so turn off flag */  
                                                                                      if (etTkHi > 0) {                       /* special condition if low order portion is 0 */  
                                                                                        OCR2A = etTkHi; TIMSK2 |= (1 << OCIE2A); /* everything regular, so just like the 16 bit */  
                                                                                      } else {                                /* if short portion is 0 the event is now */  
                                                                                        setTach(true);                        /* tach on */  
                                                                                      }  
                                                                                    }  
                                                                                    if ((ov2Tic == dlyTkLo) && (tkLo == 1)) { /* timer match and flag on */  
                                                                                      tkLo = 0;                               /* condition met, so turn off flag */  
                                                                                      if (etTkLo > 0) {                       /* everything else the same */  
                                                                                        OCR2B = etTkHi; TIMSK2 |= (1 << OCIE2B);  
                                                                                      } else {  
                                                                                        setTach(false);                      /* tach on */  
                                                                                      }  
                                                                                    }  
                                                                                }   
                                                                                ISR(TIMER1_OVF_vect) { ov1Tic = (ov1Tic + 65536L) & 0x3fff0000; }  
                                                                                /************************************************************************************************** 
                                                                                 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); }  
                                                                                /* end of file */  
                                                                                

                                                                                 

                                                                                The tach signal is used twice in the timer, because we haven't covered ignition or fueling only event timing. So the next file is the SCHEDULER.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                UWord etTkHi, etTkLo;  
                                                                                SWord dlyTkHi, dlyTkLo;  
                                                                                  
                                                                                /************************************************************************************************** 
                                                                                Generated TACH signal 
                                                                                **************************************************************************************************/  
                                                                                void ignTkHi(SLong et, SLong bt) {  
                                                                                    SLong ev = (et + bt + 4) >> 3;  
                                                                                  
                                                                                    etTkHi = (UByte)(ev & 0xff);  
                                                                                    dlyTkHi = ev & 0x07ffff00;  
                                                                                    tkHi = 1;  
                                                                                }  
                                                                                void ignTkLo(SLong et, SLong bt) {  
                                                                                    SLong ev = (et + bt + 4) >> 3;  
                                                                                  
                                                                                    etTkLo = (UByte)(ev & 0xff);  
                                                                                    dlyTkLo = ev & 0x07ffff00;  
                                                                                    tkLo = 1;  
                                                                                }  
                                                                                /************************************************************************************************** 
                                                                                Generated TACH signal first version high speed timer
                                                                                **************************************************************************************************/  
                                                                                //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 */  
                                                                                // }  
                                                                                //}  
                                                                                /************************************************************************************************** 
                                                                                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); }  
                                                                                 }  
                                                                                }
                                                                                /* end of file */
                                                                                

                                                                                 

                                                                                The latching file LATCHES.c handles the special hardware with the latching chips.

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                #include "types.h"  
                                                                                  
                                                                                #define MAX_LATCH            32  
                                                                                  
                                                                                static const UByte lchMask[] = {  
                                                                                     0x01,0x02,0x04,0x08,0x11,0x12,0x14,0x18,0x21,0x22,0x24,0x28,0x31,0x32,0x34,0x38  
                                                                                    ,0x41,0x42,0x44,0x48,0x51,0x52,0x54,0x58,0x61,0x62,0x64,0x68,0x71,0x72,0x74,0x78  
                                                                                };  
                                                                                static UByte lch[8];  
                                                                                void initLatches(void) {  
                                                                                    UByte c;  
                                                                                    DDRB &= 0xf0; DDRD &= 0x0f;  
                                                                                    for (c = 0; c < 8; c++) { lch[c] = 0; PORTB = 0; PORTD = c; PORTD = (PIND | 0x80); }  
                                                                                }  
                                                                                void setLatch(UByte n, bool t) {  
                                                                                    UByte a, b, c;  
                                                                                  
                                                                                    if (n < MAX_LATCH) {  
                                                                                        b = lchMask[n] & 0x0f; a = lchMask[n] & 0x70; c = n / 4;  
                                                                                        lch[c] = t ? (lch[c] | b) : (lch[c] & ~b); PORTB = lch[c];  
                                                                                        PORTD = (PIND & 0x0f) | a; PORTD = (PIND | 0x80);  
                                                                                    }  
                                                                                }  
                                                                                bool isLatchOn(UByte n) {  
                                                                                    return (n < MAX_LATCH ? ((lch[n>>2] & (lchMask[n] & 0x0f)) != 0) : false);  
                                                                                }  
                                                                                /* end of file */ 
                                                                                

                                                                                 

                                                                                and last, the input manager for cam and crank signals IGNCAMCRK.c

                                                                                /*************************************************************************************************** 
                                                                                 All code provided is original and developed by Jack Chaney. Any similarity to code existing in 
                                                                                 another location or form is purely coincidental. The code presented caries with it no guarantee 
                                                                                 outside my statements that IT WORKED FOR ME. 
                                                                                  
                                                                                 If, in the future this code is used in any products, please provide proper recognition for my 
                                                                                 efforts. 
                                                                                  
                                                                                 Jack Chaney, 2018 Chaney Firmware 
                                                                                 
                                                                                 History: [JAC] Original creation when the earth was cooling 
                                                                                ***************************************************************************************************/  
                                                                                  
                                                                                #include "types.h"  
                                                                                  
                                                                                #define EICRA_INIT   (1<<ISC00)|(1<<ISC10) /* Trigger INT0 and INT1 on either edge */  
                                                                                #define EIMSK_INIT   (1<<INT0)|(1<<INT1)   /* Set INT0 and INT1 active */  
                                                                                #define QUART_SEC     250                  /* provide a 250 mS timeout 1/4 second */
                                                                                  
                                                                                /* Calibration value in future, but for now just provide constants */  
                                                                                SWord getCrankToothCt(void) { return 4; }  
                                                                                SWord getCylCount(void) { return 4; }
                                                                                SWord getCylAngle(UByte) { return (16384*c); }
                                                                                
                                                                                bool isCrkRising(void) { return false; } /* these are future calibration values, but for now force the false falling edge */  
                                                                                bool isCamRising(void) { return false; }  
                                                                                bool isInt0Low(void) { return ((PIND & (1<<PD2)) != 0); }  
                                                                                bool isInt1Low(void) { return ((PIND & (1<<PD3)) != 0); }  
                                                                                  
                                                                                SLong crkTime;  
                                                                                SLong crkDiff;  
                                                                                SLong camTIme;  
                                                                                SLong camDiff;  
                                                                                  
                                                                                SWord teeth;  
                                                                                SWord rpm;  
                                                                                SWord preRpm;  
                                                                                UWord camAngle;
                                                                                UByte camError;
                                                                                UWord toothAngle;
                                                                                UByte toothError;
                                                                                UByte tmOut;
                                                                                  
                                                                                void initIgn(void) {  
                                                                                 EICRA = EICRA_INIT;  
                                                                                 EIMSK = EIMSK_INIT;  
                                                                                 crkTime = camTime = 0;  
                                                                                 toothAfterCam = 0;  
                                                                                 tmOut = 0;
                                                                                }  
                                                                                void rtUpdateIgn(void) {  
                                                                                 if (tmOut > 0) {  
                                                                                  --tmOut;  
                                                                                  rpm = preRpm / crkDiff;  
                                                                                 } else {  
                                                                                  teeth = getCrankToothCt();  
                                                                                  preRpm = TICS_PER_MIN / teeth;
                                                                                  toothAngle = DEG_PER_REV / teeth;  
                                                                                  toothError = DEG_PER_REV % teeth;
                                                                                  cylCt = (UByte)getCylCount();  
                                                                                  for (c = 0; c < cylCt; c++) {  
                                                                                    tkEv[c] = getCylAngle(c);  
                                                                                    tkSt[c] = tkEv[c] - FIVE_DEG;  
                                                                                  }  
                                                                                 }  
                                                                                }  
                                                                                /************************************************************************************************** 
                                                                                 Crank interrupt 
                                                                                 Interrupt is called on both rising and falling edges of crank signal. 
                                                                                 - Active edge is defined as falling edge, or rising edge 
                                                                                    Active edge is determined as falling and signal low, or rising and signal high. 
                                                                                 - Primary activity for the interrupt is to determine angular velocity and cam angle 
                                                                                   Using high speed clock timer, obtain current time as tempTime 
                                                                                   and using previous time (crkTime) calculate a difference between active signals. 
                                                                                 - Preserve time and difference as crkTime and crkDiff 
                                                                                **************************************************************************************************/  
                                                                                ISR(INT0_vect) {  
                                                                                 SLong tmpTime = getTime1();  
                                                                                 SLong tmpDiff = tmpTime + (tmpTime > crkTime ? 0 : 0x40000000) - crkTime;  
                                                                                /************************************************************************************************** 
                                                                                 Because the timer has a limit of a max value before rollover, the 32 bit number is limited 
                                                                                 to only using 30 bits before rollover. At rollover occurrence a factor is added to retain 
                                                                                 the proper value for DIFF. TIME is unaffected. 
                                                                                **************************************************************************************************/  
                                                                                 if (isInt0Low() ^ isCrkRising()) {  
                                                                                /************************************************************************************************** 
                                                                                  use the cam signal to locate the tooth 
                                                                                **************************************************************************************************/  
                                                                                  tmOut = QUART_SEC;  
                                                                                  camAngle += toothAngle;  
                                                                                  camError += toothError; if (toothError >= teeth) { camAngle++; camError -= teeth; }  
                                                                                  if (isCamDet()) {  
                                                                                   setCamDet(false); /* cam detected so clear the flag */  
                                                                                   camAngle = 0;  
                                                                                   camError = 0;
                                                                                  }  
                                                                                  crkTime = tmpTime;  
                                                                                  crkDiff = tmpDiff;  
                                                                                /* this doesn't provide correction for partial error correction */  
                                                                                  min = camAngle + (toothAngle / 2);  
                                                                                  max = min + toothAngle;  
                                                                                /* this is actually a good place to invoke the scheduler, as long as it isn't */  
                                                                                /* called to do too much in its operation. */  
                                                                                 }  
                                                                                }  
                                                                                /************************************************************************************************** 
                                                                                 Cam interrupt 
                                                                                **************************************************************************************************/  
                                                                                ISR(INT1_vect) {  
                                                                                 SLong tmpTime = getTime1();  
                                                                                 SLong tmpDiff = tmpTime + (tmpTime > crkTime ? 0 : 0x40000000) - camTime;  
                                                                                 if (isInt1Low() ^ isCamRising()) {  
                                                                                  setCamDet(true); /* simple signal for now */  
                                                                                  camTime = tmpTime;  
                                                                                  camDiff = tmpDiff;  
                                                                                 }  
                                                                                }  
                                                                                /* end of file */  
                                                                                

                                                                                 

                                                                                That should catch things up for now, I think I got everything, Probably should have run a compile on all this stuff to make sure, but what fun would that be. Besides, if there is something wrong, then it gives an opportunity to get questions. Otherwise, I give my 100% guarantee. it either will work or it won't. I guess the setTach() call isn't attached to anything at the moment.

                                                                                 

                                                                                Jack

                                                                                1 of 1 people found this helpful
                                                                                • Re: Engine Management
                                                                                  jack.chaney56

                                                                                  I was going to run the code when I got home from my real job. Then, I started setting up my bench, connecting up all the parts, and plugging in the power, sat back in my chair, and blinked. Three hours later, my wife told me I would probably be more comfortable sleeping in bed.  So, I didn't get a chance to compile all this stuff, and I don't have my bench set up here at work. I can provide a few extras in order to move things along, like circuits. People here seem to like circuits.

                                                                                   

                                                                                  The Nano has a bunch of pins to connect the 328 to the outside world.

                                                                                  I am not an Electrical Engineer, but I have had a lot of experience dealing with circuits and circuit design (mostly at the logic level), and things I throw together on a breadboard tend to work, so please understand any circuits I provide, would do well to have a serious design overview performed.

                                                                                   

                                                                                  With that in mind... the Nano has two sets of connecters on either side of the board. One side is primarily the A/Ds, and the other side is listed as D0 - D13. For the project, the D0 and D1 will remain reserved for serial communication (works through the USB), D2 and D3 will be the interrupt inputs for crank and cam signals (this will be circuit 1). D4 through D11 will operate the output latches (this will be circuit 2). The analog lines are tricky, and open to all sorts of opinion, so I will offer a simple solution, and let people with more knowledge make comment (circuit 3).

                                                                                   

                                                                                  Circuit 1: The circle is for 12v Vbatt (should have a resistor to regulate. The arrow is the crank or cam signal (same circuit for both). The box is the line that goes to either D2 or D3 depending. The IC is just a simple comparator (like an LM2903).

                                                                                  Circuit 2: The box on the left is a 74ls138, the input lines go from top to bottom D7 (enable), D4 (A0), D5 (A1), and D6 (A2). The outputs route through a 74ls04 hex inverter. The inverter connects to the enable 1&2 and enable 3&4 of the 75ls75 (one of 6 shown). The other inputs of the 74ls75 are connected to D8 (1D), D9 (2D), D10 (3D), and D11 (4D). The D8-D11 connect the same to all 6 of the 74ls75 ICs. If you would like, you can add another 04 and two more 75s and have 32 output lines, but 24 is satisfactory for this application.

                                                                                  Circuit 3: Analog lines are all similar and only need to provide a simple divider. The sensors that are attached are the governing elements here. The simple divider is shown. Where the circle represents some sort of variable resistance sensor, and the line on the left attaches to the A/D input. Though looking at this, it probably needs a pull up resistor someplace. Again, just a play circuit for now.

                                                                                  1 of 1 people found this helpful
                                                                                  • Re: Engine Management
                                                                                    jack.chaney56

                                                                                    My favorite word errata.  I had the chance to compile the code, and found a few "undocumented features" (bugs) in what I posted. The good news is half the files are clean.

                                                                                    A2D.c

                                                                                    EEPROM.c

                                                                                    LATCHES.c

                                                                                    and UART.c

                                                                                    The main issue with stuff was undeclared variables and constants, so start at the top.

                                                                                    types.h

                                                                                    line 71 should be DEG_PER_REV not DEGS_PER_REV

                                                                                    line 73-75 should be declared as long so add an 'L' to the end of the numbers

                                                                                    ...additions.  Because the variables are shared globally for now, need to add to the extern list

                                                                                    extern UByte tkHi, tkLo;

                                                                                    extern UByte cylCt;

                                                                                    extern UWord tkEv[];

                                                                                    extern UWord tkSt[];

                                                                                     

                                                                                    TIMERS.c

                                                                                    Like I said, setTach needed to be defined, so make a call to access one of the latches.

                                                                                    void setLatch(UByte, bool);

                                                                                    void setTach(bool t) { setLatch(23, t); }

                                                                                     

                                                                                    IGNCRKCAM.c

                                                                                    This one had a lot of things missing, mostly What I think of as loop closing stuff.

                                                                                    add line #define FIVE_DEG  466

                                                                                    add prototype SLong getTime1(void);

                                                                                    line 34: correct spelling SLong camTime not SLong camTIme

                                                                                    add some variables

                                                                                         UByte cylCt;

                                                                                         UWord tkEv[8];

                                                                                         UWord tkSt[8];

                                                                                    add a switch detection for cam detected

                                                                                         UByte camDet = 0;

                                                                                         bool isCamDet(void) { return (camDet != 0); }

                                                                                         void setCamDet(bool t) { camDet = t ? 1 : 0; }

                                                                                    line 50 remove toothAfterCam = 0; (just used for first example).

                                                                                    declare variable in rtUpdateIgn routine

                                                                                         UByte c;

                                                                                    declare variables in INT(INT0_vect)

                                                                                         UWord min, max;

                                                                                     

                                                                                    SCHEDULER.c

                                                                                    missed some stuff due to not correcting the copy paste stuff

                                                                                    add variables UByte tkHi, tkLo;

                                                                                    in schedule(...) near line 73 the for loops were missing the closing bracket '}'

                                                                                    also closing bracket for second for loop.

                                                                                    The variable name in the second for loop should be cylCt (copy paset error)

                                                                                     

                                                                                    ...and for some reason when writing a C program it is necessary to have a main function, but I didn't really provide one.

                                                                                    #include "types.h"
                                                                                    void initIgn(void);
                                                                                    void initA2D(void);
                                                                                    void initLatches(void);
                                                                                    void initTimers(void);
                                                                                    void initUart(void);
                                                                                    int main(void) {
                                                                                     initA2D();
                                                                                     initLatches();
                                                                                     initTimers();
                                                                                     initUart();
                                                                                     initIgn();
                                                                                     forever { }
                                                                                     return 0;
                                                                                    }
                                                                                    

                                                                                     

                                                                                    Something will go in the forever loop, in a bit, or you can plug in your own ideas in the meantime.

                                                                                     

                                                                                    (sloppy) Jack

                                                                                    • Re: Engine Management
                                                                                      jack.chaney56

                                                                                      clean up is done... time for fun.

                                                                                       

                                                                                      Sharp eyed folks out there might recognize, that most all the parts are in place for performing ignition management. The tach event array tkEv[] holds the TDC for each cylinder. By subtracting a value for total advance from each of the TDCs a coil event (coEv[]) angle is obtained. This is done simply in a short loop in the 1mS ignition process while the engine is running, similar to the loop to set the tkEv[] values during engine off. The value of total advance is calculated once, then applied to the tkEv in the loop. The other part of the operation is the start of dwell for each event, the dwell is a value that provides sufficient time to charge the coil, but not too much beyond fully charging the coil. This value is also captured as an angle, and the start of event (cost[]) is calculated by subtracting the dwell angle from the coil event angle. This is also done in the loop. The calculation of dwell angle is also a one time operation, and then applied in the loop.

                                                                                       

                                                                                      That wordy explanation, works out to just a few lines of code.

                                                                                      UWord coEv[8];
                                                                                      UWord coSt[8];
                                                                                      
                                                                                      void rtUpdateIgn(void) {
                                                                                      ...
                                                                                          updateTotAdv();
                                                                                          updateDwlAng();
                                                                                      ...
                                                                                          if (tmOut > 0) {
                                                                                      ...
                                                                                              for (c = 0; c < cylCt; c++) {
                                                                                                  coEv[c] = tkEv[c] - getTotAdv();
                                                                                                  coSt[c] = coEv[c] - getDwlAng();
                                                                                              }
                                                                                      ...
                                                                                      

                                                                                       

                                                                                      The two functions getTotAdv() and getDwlAng() return the angle of advance, and the angle of dwell calculated the one time.

                                                                                       

                                                                                      The last part is to include the operation in the scheduler, in the same way as the tach. The difference being, coil operation passes the cylinder number for cases where the operation is coil on plug, so the call to setCoil() has the coil number as well as the state.

                                                                                       

                                                                                                  if ((min < coSt[c]) && (coSt[c] <= max)) { ft = (SLong)(coSt[c] - pa) * bs / PART2; ignCoHi(c, ft, tm); }
                                                                                                  if ((min < coEv[c]) && (coEv[c] <= max)) { ft = (SLong)(coEv[c] - pa) * bs / PART2; ignCoLo(c, ft, tm); }
                                                                                      

                                                                                       

                                                                                      and the same for the other range of min/max

                                                                                                  if ((min < coSt[c]) || (coSt[c] <= max)) { ft = (SLong)(coSt[c] - pa) * bs / PART2; ignCoHi(c, ft, tm); }
                                                                                                  if ((min < coEv[c]) || (coEv[c] <= max)) { ft = (SLong)(coEv[c] - pa) * bs / PART2; ignCoLo(c, ft, tm); }

                                                                                       

                                                                                      The coil process takes over the fast timer operation, and the tach is moved to the slow operation.

                                                                                       

                                                                                      You might ask what values should go in total advance and dwell angle, and you are right to ask. For now, something like 20 degrees (1820) for dwell and 8 degrees (728) for dwell should be fine. To run some preliminary testing, the routines updateTotAdv() and updateDwlAng() can be stubbed out, and the getTotAdv() and getDwlAng() just return the constant values.

                                                                                       

                                                                                      Now it's getting fun.

                                                                                       

                                                                                      Jack

                                                                                      1 of 1 people found this helpful
                                                                                      • Re: Engine Management
                                                                                        jack.chaney56

                                                                                        Coming to the end of phase 1 of the project. I am attaching a zip file with the source to this stage. The next stuff starts getting into engine theory and why timing of certain events is more critical than others.... Oh dear, I have a nice zip file, and can't find how to attach it here. I will do some searching, then attach it as soon as I figure it out.  On to engine theory stuff...

                                                                                         

                                                                                        When the ignition event occurs (piston ignition), there is a downward force on the piston which is converted into a rotational force by the piston rod and crankshaft. When this happens, there is a similar force in the opposite direction which produces a wobble of the motor. Because things are generally bolted down, the motor doesn't jump out of the compartment, however the force still is there. The goal is to reduce the effect caused by this rocking motion. The solution implemented is to have the pistons fire in a sequence to balance out the forces. This is why engines implement a firing sequence Chevy uses 18436572 for an 8 cylinder motor. It is possible to keep this sequence stored someplace in an array, and just read the array value based on the sequence number in the coil firing sequence, and for many, many operations, this is the method used.  But I looked at the problem, and said, how do I make this easier for the controller. The solution became simple, just save the angle in the array properly.  For the Chevy 8 cylinder example; cylinder 1 is zero degrees, cylinder 8 is at the first position past 0, or 90 degrees. cylinder 4 is in the second positon, or 180 degrees, cylinder 3 is next at 270, etc.  When the exercise is over, the array of positions is (using our radians and the tkEv array).

                                                                                        tkEv[0] = 0;

                                                                                        tkEv[1] = 57344;

                                                                                        tkEv[2] = 24576;

                                                                                        tkEv[3] = 16384;

                                                                                        tkEv[4] = 40960;

                                                                                        tkEv[5] = 32768;

                                                                                        tkEv[6] = 49152;

                                                                                        tkEv[7] = 8192;

                                                                                         

                                                                                        Which is how it gets stored in the calibration section, along with the cylinder count (8). The calisthenics can be performed in the user interface for the calibration program. Returning to the program, the window is compared to each cell of the array and when the value is within the window, the event is scheduled. it can be seen that the counter for doing the comparison, now holds the proper cylinder number.

                                                                                         

                                                                                        Next thing to look at is the issue of "advance". For those that don't know already, in a cylinder, fuel and air are mixed to produce a flammable mixture, then under pressure, the mixture is ignited by a spark. The ignition of the fuel mixture is not instantaneous. In super slow motion, it is interesting to see the burning of the mixture occurs in a traveling wave. The goal is to maximize the impact of the wave at the point of TDC, so the greatest amount of energy is transferred to the down stroke of the power cycle. Because the rate of burn changes based on pressure and does not happen instantaneously, the command to ignite fuel happens before the cylinder reaches TDC. This is called the advance, and is measured as an angle. As the speed of the motor increases, the angle of advance also increases to compensate for the burn rate remaining generally the same. However, the rate of burn is somewhat dependent on pressure as well, so there is also a pressure compensation that needs to be factored in. Earlier in the compression cycle means the pressure value is lower, also, if the intake volume, measured by manifold air pressure (MAP), is lower or higher, the compression value changes as well. The good news is, the change in compression based on cylinder position sort of balances out because it generally increases to a stable amount and the RMS value (this is sort of based on a sinusoidal curve), stays pretty static, the MAP is the governing factor and the lower the MAP the more time is needed, so the advance increases. We have identified two components that influence the value of advance, the RPM and the MAP. The ignition is actually a chemical reaction called oxidation. Because it is a chemical reaction, the ambient temperature of the container (cylinder) also factors into rate of reaction. This means that temperature will also need to be factored into the value of total advance.

                                                                                         

                                                                                        Sounds a little like this is getting into some complex math during runtime. The rule in embedded systems is a table is always faster. So, how to use a table in this case. Quite simply, if the table is a two dimensional array, with one axis as the RPM and the other as MAP, the table can be constructed so the value of advance resides at the array cell based on the RPM and MAP coordinates. However, for a reasonable range of MAP values and RPM speeds, the array would tend to become very large. In order to keep the table size reasonable, some math is necessary. The technique is called planar interpolation. You may be familiar with linear interpolation where one value X0 and another value X1 and the f(x) values Y0 and Y1 that equate. Any point Yn along the line segment, can be calculated based on the Xn point. The equation is:

                                                                                                          Xn x (Y1 - Y0)

                                                                                        Yn = X0 + ---------------------

                                                                                                              (X1 - X0)

                                                                                         

                                                                                        Similarly this operation can be done in two dimensions. The difference is, instead of using two end points, the operation uses the four corner points of a plane. The corner points are Z0, Z1, Z2, and Z3 and relate to X0, X1, Y0, and Y1 where Z0 is the value at (X0,Y0), Z1 is at (X1,Y0), Z2 at (X0,Y1), and Z3 at (X1,Y1). The formula resolves to:

                                                                                         

                                                                                        Z0(X1-Xn)(Y1-Yn) + Z1(Xn-X0)(Y1-Yn) + Z2(X1-Xn)(Yn-Y0) + Z3(Xn-X0)(Yn-Y0)

                                                                                        ----------------------------------------------------------------------------------------------

                                                                                                                               (X1-X0)(Y1-Y0)

                                                                                         

                                                                                        Thats the formula, I'll add it to code in a little while. I want to save this so it isn't lost (lots of editing)

                                                                                         

                                                                                        Jack

                                                                                        1 of 1 people found this helpful
                                                                                        • Re: Engine Management
                                                                                          jack.chaney56

                                                                                          I found out how to attach a zip file... I had to go over to the advanced editor

                                                                                           

                                                                                          So here is the zip with the source so far.

                                                                                            • Re: Engine Management
                                                                                              fayobambam@gmail.com

                                                                                              I don't know if it has been said but there is an open source project called speeduino. Its been running engines for a while now, actually my daily runs on it. It started on the ATMEGA 2560 but its been ported to the teensy 3.6? ARM32. You could use the well developed hardware for testing purposes with your code.

                                                                                                • Re: Engine Management
                                                                                                  jack.chaney56

                                                                                                  Hi,

                                                                                                  No I had not seen that (yet), if you could post the link here, I can chase things down and see if it will give me some help with the hardware aspects of this project. My source is primarily for 328, but I also have a version that runs on 2560 that uses all shared code with the exception of 4 files.

                                                                                                   

                                                                                                  Thank you for the shout out,

                                                                                                  Jack

                                                                                                    • Re: Engine Management
                                                                                                      fayobambam@gmail.com

                                                                                                      www.speeduino.com that's the homepage. Glad to be of help

                                                                                                        • Re: Engine Management
                                                                                                          jack.chaney56

                                                                                                          Thanks a lot for the link. I looked over the site and kind of gleaned much of the information. The information provided shows they are using the Arduino interface, which might explain the reason for upgrading to the ARM processor version. The Arduino interface takes time away from the operation. I also see they are working on some CAM and Crank problems that I have already resolved and will present here. The fun thing to see is how they have a developed UI stage for updating calibration information. This is where a one man show, can be a little slow. I know what needs to be done, and I have a mental image of the interface and the code to make it work, but I just haven't done the deep dive into writing the code.

                                                                                                           

                                                                                                          My aim is to use off the shelf open source tools, so everything is repeatable by other members. So, I have started building a simplified UI using GCC and have been flip flopping between GTK+ or QT, but I am leaning to GTK because of some licensing questions I have about QT. We shall see.

                                                                                                           

                                                                                                          Thanks again for the link,

                                                                                                          Jack

                                                                                                            • Re: Engine Management
                                                                                                              fayobambam@gmail.com

                                                                                                              Not a software person myself pretty much at beginner level. The mega is the primary platform for the project and some new additions had to be made to the Arduino software side to speed up things. The injection and ignition channels are limited by the number of timers; (9) I think? The crank and cam triggers are kind of modular ;missing tooth on crank or cam is functioning properly and other patterns have been added. You may get in touch with Josh the developer if you need more info.  A more closer to the hardware approach like yours would probably utilize the available resources more efficiently. When its ready I would love to be a tester

                                                                                                                • Re: Engine Management
                                                                                                                  jack.chaney56

                                                                                                                  Hi,

                                                                                                                   

                                                                                                                  Timers can be a little obscure if you don't work with them all the time. There are actually 6 timers on the 2560 numbered  0 through 5. Timers 0 and 2 are 8 bit timers and have 8 bit compare registers. Timers 1, 3, 4 and 5 are 16 bit timers, and have 16 bit compare registers. 3, 4, and 5 also each have 3 compare registers, so that might be where the idea of 9 timers comes in.

                                                                                                                   

                                                                                                                  I invite you to read the description of how the source works for my project further down, and I am happy to answer questions about technique.  I think I am up to programming methods for doing formulas that are fast and accurate. I already covered linear and planar interpolation. I also covered use of binary radians to provide simplification of the equations. Stuff coming up is when to perform the calculations, what information is necessary, and how to generalize operations so they can become reused processes.

                                                                                                                   

                                                                                                                  Look forward to hearing more from you,

                                                                                                                  Jack

                                                                                                      • Re: Engine Management
                                                                                                        jack.chaney56

                                                                                                        A little dental work, or what to do about missing teeth.

                                                                                                         

                                                                                                        The more advanced timing wheels on motors have high tooth count and also throw in areas where teeth are deleted, in order to provide sub-timing to catch ignition in less than one full crank. It is my observation, this is overkill, because, the increase in processing needed for high level detection, often results in missing the special event, and it still requires multiple rotations to capture timing.  However, because they didn't listen to me, and implemented missing teeth anyway, coding must be made to mitigate the event.

                                                                                                         

                                                                                                        There are two processes that must be implemented, first is detection of the missing tooth event. The second is compensating for the event.  Detecting the event requires notification of the potential for a missing event, so a calibration value of missing teeth is needed. Missing teeth is not the total number of missing teeth. It is the number of missing teeth together. Meaning it is possible to have a missing tooth at 10 degrees and a second at 100 degrees, even though in total, there are two missing teeth, the value of missing teeth in calibration is still one. I hope that is clear enough.

                                                                                                         

                                                                                                        The detection of a missing tooth event, is; first, is there a missing tooth (missing > 0), and when the measured Diff value tmpDiff (current) is greater than 1.5 times the previous Diff (crkDiff). Because the change in crkDiff from tooth to tooth is relatively small, if the change in crkDiff is greater than 1.5 times, it is the indication of a missing tooth event. Once the missing tooth event has been detected, a correction to operating variables is required. First, update the cam angle, then correct for error, repeating once for each missing tooth. This operation is performed in the crank interrupt. Because it only requires comparison and addition, the impact is very slight.

                                                                                                         

                                                                                                            if (isInt0Low() ^ isCrkRising()) {
                                                                                                                tmOut = QUART_SEC;
                                                                                                        /**************************************************************************************************
                                                                                                          use the cam signal to locate the tooth
                                                                                                        **************************************************************************************************/
                                                                                                                if (isCamDet()) {
                                                                                                                    setCamDet(false);                       /* cam detected so clear the flag */
                                                                                                                    camAngle = 0;
                                                                                                                    camError = 0;
                                                                                                                } else {
                                                                                                        /**************************************************************************************************
                                                                                                                Because tooth angle may not be an even division, an error correction is performed
                                                                                                                at each update. If the error exceeds the threshold, the angle value is incremented,
                                                                                                                and the threshold value is removed from the error.
                                                                                                        **************************************************************************************************/
                                                                                                                    camAngle += toothAngle;
                                                                                                                    camError += toothError; if (toothError >= teeth) { camAngle++; camError -= teeth; }
                                                                                                                }
                                                                                                        /**************************************************************************************************
                                                                                                                Special case op for missing tooth, if no missing tooth, then skip this whole part.
                                                                                                                The detection of an event is both there is a missing tooth, and the DIFF is longer
                                                                                                                than 1.5 of the previous event DIFF.
                                                                                                        **************************************************************************************************/
                                                                                                                if ((missing > 0) && (tmpDiff > (crkDiff + (crkDiff / 2)))) {
                                                                                                        /**************************************************************************************************
                                                                                                                Missing tooth detected, so correct the angle for one missing tooth (at least)
                                                                                                        **************************************************************************************************/
                                                                                                                    camAngle += toothAngle; camError += toothEr; if (camError >= toothCt) { curAngl++; camError -= toothCt; }
                                                                                                        /**************************************************************************************************
                                                                                                                    If more missing teeth, correct again for each (math runs fast, so little or
                                                                                                                    no impact)
                                                                                                        **************************************************************************************************/
                                                                                                                    if (missing > 1) { camAngle += toothAngle; camError += toothEr; if (camError >= toothCt) { curAngl++; camError -= toothCt; } }
                                                                                                                    if (missing > 2) { camAngle += toothAngle; camError += toothEr; if (camError >= toothCt) { curAngl++; camError -= toothCt; } }
                                                                                                        /**************************************************************************************************
                                                                                                                    If there was a missing tooth the value of tmpDiff is extended, so replace
                                                                                                                    tmpDiff with the old value of DIFF to prevent calculation problems.
                                                                                                        **************************************************************************************************/
                                                                                                                    tmpDiff = crkDiff;
                                                                                                                }
                                                                                                                crkTime = tmpTime;
                                                                                                                crkDiff = tmpDiff;
                                                                                                        
                                                                                                        

                                                                                                         

                                                                                                        The value of "missing" is read from calibration at engine stop.

                                                                                                         

                                                                                                         /* Calibration value in future, but for now just provide a constant */
                                                                                                        SWord getCrankToothCt(void) { return 4; }
                                                                                                        SWord getCrankToothMissing(void) { return 0; }
                                                                                                        
                                                                                                               missing = getCrankToothMissing();

                                                                                                         

                                                                                                        TTFN,

                                                                                                        Jack

                                                                                                        1 of 1 people found this helpful
                                                                                                        • Re: Engine Management
                                                                                                          jack.chaney56

                                                                                                          The magic formula....

                                                                                                           

                                                                                                          It all starts with something called charged air mass. The steady state fueling is calculated based on mass air flow combined with a targeted air:fuel ratio. The mass of fuel is then applied to the system by injectors of fixed flow rate measured in pounds per hour.

                                                                                                           

                                                                                                          The first variable is air charge, which is the quantity of air that is moved through the system in a single period of time. In a four cycle engine, this is a single cam cycle, or two revolutions of the crank shaft  Volume is the second variable, and is relatively constant, but differs in each instance of an engine. Volume is the total displacement of the engine and is measured as the sum of the difference of each cylinder volume from lowest point to highest point in the cycle. Third is a more complex concept for a variable; Volumetric Efficiency (VE) is the effectiveness of the engine to move the total volume in a single cam cycle, and is measured as a ratio or percentage. The three variables relate as:

                                                                                                           

                                                                                                          AirCharge = Volume * VE

                                                                                                           

                                                                                                          Next is the conversion from air charge to air mass. the ratio between air charge and air mass is rho. The value is comprised of the volume of air and the mass of air. The mass of air is considered as dry air and because air density is relevant, the barometric pressure will be factored into the equation:

                                                                                                           

                                                                                                                      AirMass

                                                                                                          rho = --------------

                                                                                                                    AirCharge

                                                                                                           

                                                                                                          Now it is necessary to add in the ideal gas law to provide a base for calculations:

                                                                                                           

                                                                                                          pV = nRT

                                                                                                           

                                                                                                          But the value of rho is a dynamic value based on the volume flow of air mass. The static value of air mass has been measured extensively and is provided in the Handbook of Chemistry and Physics.
                                                                                                          (weight of dry air at sea level 28.966 g/mol::F-201 Handbook of Chemistry and Physics 56th ed.) This is the value of n, and V is the total displacement of the engine. Because the system is dynamic, the equivalent component for rho is:

                                                                                                           

                                                                                                                      n
                                                                                                          rho := ---
                                                                                                                      V

                                                                                                          Last is the controllable element of the operation. The equation's goal is calculating fuel mass. The mass of fuel is combined with the volume of air based on a target ratio of fuel to air, or the Air-Fuel Ratio(A:F)

                                                                                                           

                                                                                                                      AirMass
                                                                                                          A:F = --------------  and  AirMass = AirCharge * rho
                                                                                                                     FuelMass

                                                                                                           

                                                                                                                               AirCharge * rho
                                                                                                          FuelMass = ----------------------
                                                                                                                                         A:F

                                                                                                           

                                                                                                          Air charge can be replaced with Volume * VE, and because, from the ideal gas law, rho equates to:

                                                                                                           

                                                                                                                       p
                                                                                                          rho = --------
                                                                                                                     R * T

                                                                                                           

                                                                                                          The equation for fuel mass becomes:

                                                                                                           

                                                                                                                               Volume * VE * p
                                                                                                          FuelMass = ----------------------
                                                                                                                                    A:F * R * T

                                                                                                           

                                                                                                          In the total of the equation, there is one constant R which is the ideal gas constant and has the value: Calculated from base information Boltzmann's Constant k = 1.3807e-23 J/K combined with Avagadro's Number N(a) = 6.0220e23 /mol provides a value:

                                                                                                           

                                                                                                          k * N(a) = 8.3145754 J/mol-K

                                                                                                           

                                                                                                          - The value of pressure is absolute and is measured in Newtons per square meter (Pascals).

                                                                                                          - Temperature is absolute and measured in degrees Kelvin, VE and A:F are both ratios and have no units, and because 1 Joule => 1 m3-Pa, the measurement for volume is in cubic meters.

                                                                                                           

                                                                                                          The last part of the process is to eliminate units based on fuel mass:

                                                                                                           

                                                                                                                                         m3 * Pa

                                                                                                          FuelMass => --------------------------  => mol

                                                                                                                                  m3-Pa/mol-K * K

                                                                                                           

                                                                                                          Then multiplying the value by the mass of dry air, in g/mol, yields the fuel mass in grams, and the final equation resolves to:

                                                                                                           

                                                                                                                                Volume * VE * p * 28.966
                                                                                                          FuelMass = -----------------------------------
                                                                                                                                    A:F * T * 8.3145754

                                                                                                           

                                                                                                          The units for each of the items is:

                                                                                                           

                                                                                                          FuelMass - grams

                                                                                                          Volume - cubic meter

                                                                                                          p - Pascals

                                                                                                          T - degrees Kelvin

                                                                                                           

                                                                                                          To provide workable sized variables, so the intermediate values don't get too big or too small to manage with integers only, some base 10 elements are implemented. Vehicle engines have their displacement rated in cubic inches or liters, and often in cubic centimeter. Conversions to all these units are readily available, and since the equation started with cubic meters, cubic centimeter (cc) is chosen to provide sufficient resolution. Pressure sensors are rated in kilo-Pascals or often BAR (base atmosphere), so the selection for pressure is kilo-Pascal (kPa). Because temperature is in degrees kelvin, there is no need to change to another unit, and leave conversion to Centigrade (C) or Fahrenheit (F) to the user interface. Last, because grams is a very large value in fueling, the fuel mass will be in miligrams.

                                                                                                          Conversions:


                                                                                                          grams = 103 miligrams
                                                                                                          cubic meter = 106 cubic centimeter
                                                                                                          Pascal = 10-3 kilo-Pascals

                                                                                                           

                                                                                                                                     V * VE * P * 28.966 * 10^3
                                                                                                          FuelMass = ------------------------------------------------
                                                                                                                                A:F * T * 8.3145754 * 10^6 * 10^-3

                                                                                                           

                                                                                                          Leaving a single constant 3.483761781

                                                                                                           

                                                                                                          Now for my stump speech about not using floating point...

                                                                                                           

                                                                                                          Don't use floating point for embedded processes. The conversions eat up all sorts of valuable time, and it is really quite unnecessary if the operations are well planned.  For the fueling equation, VE and A:F are both ratios, so any value used for the fixed point method will divide out. In other words, if you multiply a ratio by 1000 so that 99.9% becomes 999 integer value, if the same multiplier is used in the numerator and denominator, they will cancel each other out. If temperature is stored as an absolute value K * 100 and pressure is stored as kilo-pascals * 100, the same elimination will occur. This leaves only the constant 3.483761781.

                                                                                                           

                                                                                                          Planning this out, it is possible to use a numerator portion and a denominator portion. When I do this type of operation, I like to use either the numerator or the denominator as a power of 2. This way, the multiplication, or division operation is resolved to a simple logical shift right or left.  After some playing with values, I came up with

                                                                                                           

                                                                                                          16384

                                                                                                          --------- =  3.483733787 (I think that's close enough)

                                                                                                            4703

                                                                                                           

                                                                                                          So now the total fueling calculation only has the penalty of multiplication and division. If the calculations are done in a proper sequence, the error loss is minimized. The result is fuel mass in mg per CAM cycle.

                                                                                                           

                                                                                                          FuelMass = Volume * VE;

                                                                                                          FuelMass /= AF;

                                                                                                          FuelMass *= PortPressure;

                                                                                                          FuelMass /= PortTemp;

                                                                                                          FuelMass *= 16384;

                                                                                                          FuelMass /= 4703;

                                                                                                           

                                                                                                          Because of the values involved, it is a good idea to use a 32bit long for all the intermediate values (casting when necessary).

                                                                                                           

                                                                                                          Because injectors are rated in pounds per hour, a conversion to mg is needed. The value in calibration is all that is necessary so the conversion can be performed at the UI level.

                                                                                                          453.592 grams per pound

                                                                                                          3600 seconds per hour

                                                                                                           

                                                                                                          1 lb/hr = 125.998 mg/s

                                                                                                           

                                                                                                          Build this into a spreadsheet and play around with the numbers and let me know what you find.

                                                                                                           

                                                                                                          Jack

                                                                                                          2 of 2 people found this helpful
                                                                                                            • Re: Engine Management
                                                                                                              jack.chaney56

                                                                                                              A little more math calisthenics. The injectors are rated in lb/hr, but are stored in calibration in mg/s so a 40# injector is stored as 5040 (5039.92 rounded integer). Remembering the scheduler resolves to system tics (2MHz). The pulse width should be calculated as number of tics and still retain significant digits using integer math.  The total possible volume is a summation of all the injectors.

                                                                                                               

                                                                                                              The total fuel mass per cam cycle is in mg. The total pulse width per cam cycle then is:

                                                                                                               

                                                                                                                   fuel mass * TICS_PER_SEC

                                                                                                                   -------------------------------------

                                                                                                                       total injector volume

                                                                                                               

                                                                                                              For batch and staggered, the pulse is done twice so the pulse width is divided in two.  For TBI the pulse is done four times so the pulse width is divided by 4.

                                                                                                               

                                                                                                              When to do calculations is now what is necessary. The formula provided is for steady state fueling, where there are no changes to demand or RPM, and the A:F ratio remains generally constant.  The equations can be performed generally at any time, meaning they do not need to be done in the tooth interrupt. However, the volume of fuel required does change and needs to be updated regularly and at a high enough rate to maintain smooth operation.  Examining the information, it is possible to not some items that do not change during runtime, and can be performed during engine stop, summation of injectors as an example.

                                                                                                               

                                                                                                              For the timing of events, the scheduler is employed in exactly the same manner as before, for TBI and staggered batch, four evenly spaced events, and for standard batch two evenly spaced events. The last case of sequential fueling, needs to activate the injector during the intake stroke of the cylinder. This begins at the top of the exhaust stroke, which is 360 degrees from TDC, which is the value of the tach event from ignition timing.  Using another array for the injector start point, it is a simple case to add provision for injector scheduling the same as with ignition.

                                                                                                               

                                                                                                              I kind of glossed over something from before, the value of advance was determined in units of degrees, and I kind of fudged the value for dwell. In actuality, dwell is a time value for how long it takes to bring the coil to a full charge. Since the operation of the scheduler is based on angle, it is necessary to convert the dwell time into a dwell angle.  This is done using the magic number "base" described before. The value of base was used in the scheduling operation to convert the value of angle to time, but it can also be used in the reverse operation.  Because the dwell time value doesn't change significantly, dwell angle only needs to be calculated as speed changes, usually in the mS routine.  It is also possible at the same time to do the same operation with pulse width. Dividing by 2 (batch) or 4 (TBI) can be done during the scheduling event, because it is only a right shift operation. If at this point it starts to look like the 1mS operation is getting too fat with processes, it is always possible to run the calculations interleaved.  Doing the ignition calculations on one pass, then the fueling calculation on the second pass. This is something that would require some physical testing to see if it is a concern.  Time to run the experiment.

                                                                                                               

                                                                                                              Jack

                                                                                                              • Re: Engine Management
                                                                                                                mp2100

                                                                                                                This is (all of it) quite a brain dump, thanks.  When this is all done, I'll start designing engines (no, not really, is joke . . .)

                                                                                                                 

                                                                                                                I understand and agree about the integer/floating point discussion.  When optimizing program execution time, integers are soooo much faster.   And multiplying y=x*x*x is way faster than using y=x^3.  [I learned this on a motorola 68k processor (PLC, not macintosh)]  Do the integer math for all the intermediate calculation.   When (if) you need precision, yes, do floating point at the end.

                                                                                                                  • Re: Engine Management
                                                                                                                    jack.chaney56

                                                                                                                    Yes, thank you Allen.

                                                                                                                     

                                                                                                                    I have a general 16 bit rule book that I use.

                                                                                                                    The 16 bit solution
                                                                                                                    All data elements have like conversions, and a single set of units.

                                                                                                                    All conversions are done externally to the device, or at the UI level.

                                                                                                                    Values are all maintained as 16 bit integers. types are:
                                                                                                                      - Temperature -> absolute degrees K * 100 (0 degrees C stored as 27315)
                                                                                                                      - Angle in binary radians 360 converts to 32768
                                                                                                                      - Ratios are all fixed point * 1024 or 100% == 1024
                                                                                                                      - Pressure is stored in kPa * 100 absolute (1 atm stored as 10129)

                                                                                                                     

                                                                                                                    Limits of accuracy for equations are imposed based on 16 bit values
                                                                                                                      - angles (error +/- 0.0109 degrees)
                                                                                                                      - ratios < 6399.90% for unsigned or 3199.90% for signed (error +/- 0.049%)
                                                                                                                      - temperature < 655.35 degrees K or 382.20 degrees C (error +/- 0.005 degree C)
                                                                                                                      - pressure < 655.35 kPa or 6.47 atm (error +/- 50Pa)

                                                                                                                     

                                                                                                                    This manages a significant amount of the measurement. Any cases where the values or accuracy are exceeded becomes a special case and happens very rarely.

                                                                                                                     

                                                                                                                    Jack

                                                                                                                • Re: Engine Management
                                                                                                                  jack.chaney56

                                                                                                                  Something that I wanted to point out, because there have been several suggestions for using a variety of other processors. If you look at the code provided, the great majority of the operational components are not dependent on any particular processor. As long as there are A/Ds available, and some mechanism for timer control, all the rest is fully portable.  In fact with the exception of four files, (with minor differences) the same code runs on ATMega328 and ATMega2560.  The code for the Cam and Crank operation only requires an interrupt vector point for a trigger line for the signals.

                                                                                                                   

                                                                                                                  Next part will start to cover exceptions, changes, and asynchronous operation.  Also, how to manage calibration and monitoring.

                                                                                                                   

                                                                                                                  Jack