Can the GPIO run variable cam phasing?
Can the GPIO run variable cam phasing?
Re: Can the GPIO run variable cam phasing?
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.
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
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
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.
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
Lance.
-
- Posts: 55
- Joined: Thu Apr 17, 2008 5:19 am
Re: Can the GPIO run variable cam phasing?
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
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.
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
- 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.
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
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;
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;
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 *********************
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)
};
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
};
We also have to go to the top of the INI and find a spot that says:
Code: Select all
pageSize = 538, 30
Code: Select all
pageSize = 546, 30
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"
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"
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
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.
Re: Can the GPIO run variable cam phasing?
Off topic... I had Dr. Huang as a professor in college. He taught, oddly enough, my microcontrollers course. Small world.
-
- Posts: 1696
- Joined: Fri Apr 04, 2008 1:28 pm
Re: Can the GPIO run variable cam phasing?
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.