Can the GPIO run variable cam phasing?

A forum for discussing the applications and implementations of the DIY general code template for the GPIO and its 25 user inputs and outputs (+ serial & CAN). The code is 99% complete, the user just has to add their own conditional I/O logic and compile (with a free special Edition of CodeWarrior).
gte
Posts: 4
Joined: Fri Jul 11, 2008 10:48 am

Can the GPIO run variable cam phasing?

Post by gte »

Will this be able to work with vvt aka variable cam phasing?
ca434sbc4
Posts: 8
Joined: Sun Sep 14, 2008 8:43 pm

Re: Can the GPIO run variable cam phasing?

Post by ca434sbc4 »

The GPIO hardware has the right inputs and outputs to do the job what is missing is the control s/w to get the job done. From my perspective in general the MS project could use more embedded programmers to make things like this happen.

One problem is embedded programmers are a rare bunch, most s/w schools focus on desktop programming with C++ or other high level languages. To generate fast and effective code much of the time critical functions need to be written in assembler. You mention assembler to most desktop programmers and their eyes glaze over.

Perhaps Al, Bruce and Lance could start a positive discussion if folks want to contribute embedded s/w help particularly for the GPIO board since its so flexible.
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

Everyone,

The MShift code (http://www.msgpio.com/manuals/mshift/4L60Ecode.html) is designed to be an educational tool for users to learn how to write embedded code. That means it has a LOT of comments, tips, and background material, as well as links to compilers and tutorials. It also serves as a collection of examples of how to do various things.

It is not necessary (or even necessarily desirable) to write code in assembly language if the application does not need to be timing critical (i.e., microsecond accuracy), and the MShift code is written almost entirely in C (and runs at several thousand main loops per second). That's intended to make it easier for people to get up to speed. There is even a free 'special edition' of the Codewarrior compiler (http://www.freescale.com/webapp/sps/sit ... NS&tid=CWH) for users (and it is much easier to set up than GCC, for example). (You do have to register, though.)

If people are familiar with C (and there are lots of programming references on the web and in bookstores on the C language) the main additional material is understanding and reading/writing directly from/to the various registers (both to turn things off and on, and to set up PWM, ADCs, etc.). There is a very good book on programming the 9S12, it is:

"The HCS12/9S12 : An Introduction to Hardware and Software Interfacing" by Han-Way Huang

Hardcover: 880 pages
Publisher: Delmar Learning
Language: English
ISBN-10: 1401898122
ISBN-13: 978-1401898120

and has examples of all functions (ADC, PWM, Input Capture, Output Compare, etc.) in both assembly language and C. None of the is 'easy'. However, none of it is impossible for anyone who can manage to successfully tune an EFI system.

For an example of how the programming works, we want to add (as yet unwritten) 'refresh' cycles to the PWM of the outputs. The output PWM is controlled by two 'variables' for each channel: PWMPERx and PWMDTYx (where x is 1 to 4 and the values can range from 0 to 255). Technically these are "registers", and are defined at specific memory addresses where the processor will look for them. In this case we can treat them like variables, though. PWMPERx is the period of channel x in 'clock tics' (we set up the duration of the clock tics separately in the program). PWMDTYx is the number of tics the output is held high (or low if you choose to change from the default value). So some PWM percentages are:

0% - PWMDTYx = 0;
50% - PWMDTYx = PWMPER/2;
100% - PWMDTYx = PWMPERx;

and in general PWMDTYx = (target%) * PWMPERx/100;

In the case of the refresh cycle, we want the PWMDTYx to go to 100 for M milliseconds every N milliseconds. In the code there is a section of the code (ISR_Timer_Clock - an interrupt) that is executed every millisecond (as well as sections that execute at other intervals). So all we have to do is if PWMDTYx does not equal zero, then start a counter (just a variable that adds one each time the millisecond code is executed). While this counter is less than N (let the PWMDTYx be set in the main loop), set a flag to let main loop control PWMDTYx. When the counter reaches N, set the PWMDTYx to PWMPERx, and set a flag to disable PWMDTYx setting in the main loop. Then when the counter reaches M+N, reset the counter to zero (and let the main loop control). So off the top of my head, the 'pseudo code' looks like this:

Code: Select all

// in millisecond section
if (PWMDTYx > 0) // i.e. if the output x is turned on, otherwise leave it off
  {
  pwm_refresh_count++;  //increment the counter by one - is equivalent to: pwm_refresh_count = pwm_refresh_count +1;
  if (pwm_refresh_count < N)
    { pwm_disable_flag = 0; // clear flag }
  else
    { PWMDTYx = PWMPERx; // set to 100% to refresh the solenoid charge
       pwm_disable_flag = 1; // set flag - will use in main loop to disable lookup of PWM in tables, etc.}
  // endif
  if (pwm_refresh_count) > (M+N-1)) 
    { pwm_refresh_count = 0; // reset the counter}
  } // end if PWMDTYx > 0
(comments start with // (or are enclosed in /* ... */) and are ignored, but are helpful to explain what's going on)

Note that the variable/counter/flag names are essentially arbitrary (within limits that C imposes), we would have to define them somewhere (as char, int or long). The names for the PWM channels are standard (and defined in the HCS12DEF.H file), and are PWMPER1, PWMDTY1, PWMPER2, PWMDTY2, PWMPER3, PWMDTY3, ... Again there's lots in the comments of the code itself.

That's all there is to it really. Most added functions and features are like this. They are simple to implement (but not always to troubleshoot, and you will spend much more time debugging than writing new code). I am sure there are many, many users out there who could write this sort of thing, and the GPIO is intended to be a platform to get them started.

The code itself is divided into four main sections: compiler instructions, processor set-up, the main loop, and interrupts. The compiler instructions tells the compiler how to arrange things in memory, etc., and this is where you add new variables.

--> To add an input variable, you must define it as part of the inpram (or in2ram) structure in the code (as an char, int, or long - there's extensive commenting on this in the code). Then you also need to add it to the megasquirt-II.ini in TWO places. The first is in the [Constants] section, where it must match the inpram location ("memory offset" and type: U08 for unsigned char, S16 for signed integer, etc.). The 'offset' must match the cumulative total from the beginning of inpram. There's more on this in comments in the ini itself. After that, you have to add the variable name to a menu - there are lots of examples in the ini itself.

--> To add an output variable, you must define it as part of the outpc. structure in the code (again char, int, long) and to the [OutputChannels] in the ini. You can also then create gauges and add the value ([GaugeConfigurations]) to the datalog ([Datalog]).

In the example, both M and N are input variables, so we would add them to the inpram structure in the code (as type unsigned char since we only need them to run from 0 to 255), and the [Constants] as U08 as well as a menu in the ini. We wouldn't have any output variables in the example.

Note that both input and output variables have scaling factors, so you can use 3156 in the code but have it come up as 3.156 in MegaTune by using a scaling factor of 0.001 (this is handy since the processor can't handle fractions, so everything has to be a multiple of 1 at all times - so if you did (1/5)*500 you would get zero since 1/5 would go to zero and 500*0 is zero, whereas 1*(500/5) would be 100 - so you have to be careful sometimes with the math).

I am happy to answer specific questions on this forum on the code, for those that have tried to read the background materials.

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

If people find it helpful, I can document some of the functions I add to the code as I develop them. That is, I will do the initial thoughts (code purpose and general operation), the pseudo code, and the actual code (much as in the example above) so that people can see how easy (or hard) this actually is. I feel I am in a good position to do this, since I am definitely not a programmer, and thus take little for granted when writing code. Let me know if you might find this useful.

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
Matt Cramer
Posts: 55
Joined: Thu Apr 17, 2008 5:19 am

Re: Can the GPIO run variable cam phasing?

Post by Matt Cramer »

I certainly would find it helpful; I'm not very good at programming things myself.
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

Matt,

Okay, I will post on various new developments so people can see how to go about it (from the initial thoughts to the code to the debugging process). I have been distracted elsewhere for the last few weeks, but hope to get back to the Mshift code 'full-speed' shortly, and will post here regularly as that unfolds.

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

To follow on from the example above, here are a few more considerations in writing the actual code:

- we probably want to use a PWM refresh on the pressure control solenoid - GM calls this the 'dither' cleaning pulse,

- we might want to use the refresh on the TCC (but not for now),

- we likely want to use refresh on the output #1 and #2 (the shift control solenoids), however these are not PWM channels. To create 'bit-banged' PWM on these outputs, we can turn them alternately off and on ("toggle") with the clock interrupt (for example, if we want 500 Hz, we toggle it every millisecond - as long as the output is 'active').

In writing the code, we'll use a number of variables to set the refresh behaviour of the outputs. We preface the variable names with "dither_" so they are easy to spot in the code. The variables we might use are:

- dither_int1: the interval time between dithers for output1 (sol A) - analogous to N above
- dither_int2: the interval time between dithers for output2 (sol B)
- dither_intPC: the interval time between dithers for the pressure control output

(note that int here hints at 'interval' - it doesn't necessarily mean the variable is declared as a 16-bit integer)

- dither_dur1: the 100% duty cycle time for output1 - analogous to M above
- dither_dur2: the 100% duty cycle time for output2
- dither_durPC: the 100% duty cycle time for the pressure control output

However, lets assume dither_int1 == dither_int2 = dither_int, and dither_dur1 == dither_dir2 = dither_int (we can always change this later easily in the code, but doing this reduces the code complexity, RAM requirements, and INI size).

Also, since we will be toggling these outputs in the interrupts, we will make these variables 'global', meaning that they can be accessed from any part of the code. Because they are inputs from MegaTune to the controller they will go into the inpram structure, we will put them at the end. We have to decide if they will be char (0-255), ints (0-65535) on long (0 to 2³²). On the OEM application, GM runs the PC dither every ten seconds (I can't recall off the top of my head, it might be one second of dither every ten seconds - I need to look this up before going much further). On the 41te the output is 1.96 kHz, with an 8 millisecond refresh every 50 milliseconds.

So on the face of it, we need to go from around 8 to around 10000 milliseconds, and ints would work. On the other hand, we could use chars, and have the 0-255 represent milliseconds on output1 and output2, and have the value represent seconds on the PC output. This saves us 4 bytes (chars are one byte==8bits, ints are two bytes==16 bits, and longs are 4 bytes==32 bits), but makes the settings less flexible (for example, what if a user wanted to use the pressure control output for something else?). So after considering this for a while, we might decide it's worth it to use ints (we can always degrade them later if necessary).

These values need to go into the global section of the code, and into the INI as well, and I will post a bit on that in a while. I will also post an example of bit-banging to PWM output1 and output2.

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

In order to have user-input values, we have to let both the GPIO code and MegaTune know what and where they will be. The first thing we will do is add the variables to the code itself. they will go into the inpram structure ("in pram" is a name Al made up to suggest 'input parameters'). The existing structure looks like this:

Code: Select all

typedef struct {
...
unsigned char open_time;        // injector open time (msecX100) for mileage calcs
unsigned int fuel_density;      // fuel density (pounds/gallon)
unsigned char nSquirts;         // number of squirt (divide by 2 if alternating)
} inputs1;
We want to add refresh_int, refresh_dur, dither_intPC, and dither_durPC (note the slight name change to be clearer), all as ints to get:

Code: Select all

typedef struct {
...
unsigned char open_time;        // injector open time (msecX100) for mileage calcs
unsigned int fuel_density;      // fuel density (pounds/gallon)
unsigned char nSquirts;         // number of squirt (divide by 2 if alternating)

unsigned int refresh_int;        // the interval time (msec) between dithers for output1 and output2
unsigned int refresh_dur;        // the 100% duty cycle time for output1 and output2
unsigned int dither_intPC;      // the interval time (msec) between dithers for the pressure control output
unsigned int dither_durPC;      // the 100% duty cycle time for the pressure control output
} inputs1;
(Note that the blank line is fine, the compiler will ignore it.)

Next we have to add the variable to the INI. In the megasquirt-II.ini [Constants] section (near the top of the file), the input parameters are listed in order that they appear in the inpram structure in the code. They don't have to have the same names (though this is certainly clearer), because the important thing is the memory 'offset (the number of bytes from the beginning of the structure). You will see they are listed in order, with the offset climbing from 0 to 537:

Code: Select all

 ;name             = class,  type, offset,         shape,    units,       scale,  translate,     lo,     hi,   decimal digits
  InputCaptureEdge = bits,    U08,     0,          [0:0], "Rising Edge", "Falling Edge"
...
  nSquirts              = scalar,  U08,   537,                "",          1.00,    0.0000,      1.0, 16.000,        0    ; number of squirts (divide by 2 if alternating)

; ****************************************************************** end inpram *********************

We want to add the 4 variables. Since we declared these as unsigned ints, they will be U16 (unsigned, 16 bits) in the INI:

Code: Select all

 ;name             = class,  type, offset,         shape,    units,       scale,  translate,     lo,     hi,   decimal digits
  InputCaptureEdge = bits,    U08,     0,          [0:0], "Rising Edge", "Falling Edge"
...
  nSquirts              = scalar,  U08,   537,                "",          1.00,    0.0000,      1.0, 16.000,        0    ; number of squirts (divide by 2 if alternating)
  refresh_int            = scalar,  U16,   538,                "msec",      1.00,    0.0000,       10,  20000,        0    ; the interval time (msec) between dithers for output1 and output2
  refresh_dur            = scalar,  U16,   540,                "msec",      1.00,    0.0000,        0,  10000,        0    ; the 100% duty cycle time for output1 and output2
  dither_intPC          = scalar,  U16,   542,                "msec",      1.00,    0.0000,       10,  20000,        0    ; the interval time (msec) between dithers for the pressure control output
  dither_durPC          = scalar,  U16,   544,                "msec",      1.00,    0.0000,        0,  10000,        0    ; the 100% duty cycle time for the pressure control output

; ****************************************************************** end inpram *********************
In this case we have used scaling factors of 1, and no translations (the sort of thing you would use to convert tenths of a millisecond in the code to milliseconds in MegaTune, or convert Fahrenheit to Celsius). We allow zero for the durations - this will allow use to disable the refresh/dither (without using another variable to set it). However, we won't allow zero for the intervals - users have to specify something sensible.

We also have to add default values. These appear in a section below the inpram structure in the in1flash structure:

Code: Select all

// Assign values to the in1flash parameters
const inputs1 in1flash = {
0,                // input capture edge for VSS, 0=rising edge, 1 = falling edge
{10,              // vss_table[MPH no = 0] , MPH (KPH), use for 12x12 auto shift table
  20,30,40,50,60,70,80,90,100,120,140},
...
6073,        // fuel density (pounds/gallon)
1            // number of squirts (divide by 2 if alternating)        
};
becomes:

Code: Select all

// Assign values to the in1flash parameters
const inputs1 in1flash = {
0,                // input capture edge for VSS, 0=rising edge, 1 = falling edge
{10,              // vss_table[MPH no = 0] , MPH (KPH), use for 12x12 auto shift table
  20,30,40,50,60,70,80,90,100,120,140},
...
6073,        // fuel density (pounds/gallon)
1,           // number of squirts (divide by 2 if alternating)        
50,8,10000,1000 // Refresh/Dither default values
};
(Note the comma added after the nsquirts value of 1 - it must be separated from the following value we are adding)

We also have to go to the top of the INI and find a spot that says:

Code: Select all

   pageSize            = 538,            30
and change this to:

Code: Select all

   pageSize            = 546,            30
because the page size (in bytes) is now larger, the last value is at 544 and is 2 bytes long, so the total is 546 (MegaTune will give you a warning if you don't do this.)

Finally, we have to add the variables to a menu so we can see an set them. These could go anywhere, but we will create a separate sub-menu for them. In a section like:

Code: Select all

   menuDialog = main
      menu = "&General Settings"
         subMenu = revlimits,      "&Rev Limits"
         subMenu = gearRatios,     "&Gear Ratios" 
         subMenu = shiftFactors,   "&Shift Factors"
         subMenu = retards,        "T&iming Adjustments"
         subMenu = PWMSettings,    "Solenoid &PWM Setup"
         subMenu = VSSSettings,    "&VSS Setup"
         subMenu = TCCSettings,    "&TCC Settings"
         subMenu = stdIN,          "Standard Inputs Configuration"
;         subMenu = drag,           "Drag Race Logging Configuration"
         subMenu = noCANload,      "no-CAN &Load Calibration",      0,   { !CAN_enabled }
         subMenu = Units,          "&Units"
we add another entry to let us open the menu we will make:

Code: Select all

   menuDialog = main
      menu = "&General Settings"
         subMenu = revlimits,      "&Rev Limits"
         subMenu = gearRatios,     "&Gear Ratios" 
         subMenu = shiftFactors,   "&Shift Factors"
         subMenu = retards,        "T&iming Adjustments"
         subMenu = PWMSettings,    "Solenoid &PWM Setup"
         subMenu = VSSSettings,    "&VSS Setup"
         subMenu = TCCSettings,    "&TCC Settings"
         subMenu = stdIN,          "Standard Inputs Configuration"
         subMenu = Dither,         "PWM Refresh/Dither"
;         subMenu = drag,           "Drag Race Logging Configuration"
         subMenu = noCANload,      "no-CAN &Load Calibration",      0,   { !CAN_enabled }
         subMenu = Units,          "&Units"
then create the menu:

Code: Select all

   dialog = Dither, "PWM Refresh and Dithering"
      field = "Refresh Interval",        refresh_int
      field = "Refresh Duration",        refresh_dur
      field = "PC Dither Interval",      dither_intPC
      field = "PC Dither Duration",      dither_durPC
(See the other menus for examples.)

In all cases the string in quotes is what MegaTune display as text, and the variable name is the value that appears as an editable value.

At this point you should probably update the date/time in the ini (near the very top). Then you should be ready to use the variable in the code, and set them in MegaTune!

If you get MegaTune warnings, if the comms are very slow, or the fields are full of zeros when they shouldn't be then you likely have messed up the offsets somewhere in the INI and will need to go over them with a fine tooth comb. The advantage of always adding the values at the end is that if you had it working before you added the variables, you know the mistake must be somewhere in the values you added.

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
carbon
Posts: 1
Joined: Tue Mar 24, 2009 7:21 pm

Re: Can the GPIO run variable cam phasing?

Post by carbon »

"The HCS12/9S12 : An Introduction to Hardware and Software Interfacing" by Han-Way Huang

Off topic... I had Dr. Huang as a professor in college. He taught, oddly enough, my microcontrollers course. :D Small world. :D
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

carbon,

Yeah, Dr.Huang must have endless energy - he's done a bunch of comprehensive microcontroller texts. I'd love to take his class now, knowing what I already know!

Lance.
"Never wrestle with pigs. You both get dirty and the pig likes it." - George Bernard Shaw
Post Reply