Page 2 of 4

Re: Can the GPIO run variable cam phasing?

Posted: Thu Mar 26, 2009 11:46 am
by Bernard Fife
Back to the topic at hand (adding PWM refresh and dither to the outputs):

Why do we add the variables to the code and MegaTune first, why not do it later? You could do that, but there are a few reasons to do it first:

1. One of the better ways to error check the code syntax is compile it often. You can only compile the code properly if all the variables used are declared. I usually compile after adding every complete statement (a whole if statement, etc.) This really helps to nip potential errors in the bud - partly because the compiler doesn't get the location of the error mixed up by a series of coding issues, and partly because if you get an error (and you didn't have any before), you know the error has to be in the statement you just wrote. You don't pay extra for compiling code, so do it often (it only takes a couple of seconds).

2. As soon as you have any useful code, you can check it in MegaTune (or MS TunerStudio, etc.) to see if the variables display properly, etc. So you can do program debugging right from the start, before you have hundreds or thousands of lines of code.

The code has a spare variable built in called outpc.dbug. This is already set up in MegaTune, and has a gauge, it's in the datalog, etc. You can use this variable to get info on the state of the code as you write mods. For example, you might have a statement like:

Code: Select all

outpc.dbug++; // increment dbug - this is equivalent to outpc.dbug =  outpc.dbug+1
inside a loop you write to use MegaTune to see if the loop is ever called (and how many times). Or you could set it to a value to see the internal calculations. Suppose we wanted to know what the duty cycle was set to, we could could use:

Code: Select all

outpc.dbug = (PWMDTY3 * 100)/PWMPER3; // output the duty cycle percent of PWM channel 3
outpc.dbug is mostly for troubleshooting. If you want to add a value to the output variables permanently, you should add it to the outpc. structure (I will cover this shortly).

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Mon Mar 30, 2009 9:05 am
by Bernard Fife
Now that we have the variables in place, we have three things we need to add code for:

1. The dither on the pressure control output, this is the easiest since the output is already set up using PWM, and we can use the ideas earlier in the thread to switch the PWM from the lookup table value to 100% at the user specified intervals.
2. Turn the solA/solB (aka. output1/output2) into PWM outputs by 'bit-banging' the outputs at a user frequency, this will require toggling the outputs in the timer interrupt section of the code when (and only when) they are on. We will have some decisions to make at that point about what sort of frequencies we will allow.
3. The refresh on solA/solB (aka. output1/output2). We will handle this a little differently than the pressure control because they are not native PWM outputs. We will toggle the output if it is on with an 'exclusive or' operator, but set it high if the refresh counter is in the appropriate range.

Shortly I will work through these in order in some detail in this thread.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Mon Mar 30, 2009 12:59 pm
by Bernard Fife
In setting the dither (cleaning) pulses for the pressure control output, there are a number of things we will need to keep in mind:

- we don't want the pressure to 'dither' during a shift (as we may have set a lower than 100% maximum shift pressure), we can check for a pending shift if outpc.current_gear does not equal outpc.target_gear,
- we probably don't want the dither pulse while in manual mode (partly because we won't necessarily know when the next shift is coming, and partly because we are likely to be in the high pressure ranges (i.e. driving hard) anyways),
- we may only want the dither pulses if the average long term load (outpc.load_long) is low,
- the pressure control output PWM registers are PWMPER2 and PWMDTY2, and these correspond to port PT2 (and GPIO board circuit PWM3), to set the duty cycle to 100% we set: PWMDTY2 = PWMPER2;
- we will also need to set a flag when the dither pulse is on (we'll call it "dither_flag" - not overly clever!) so that when the dither_flag bit one is set (i.e. the dither_flag variable is set to not be zero) we will skip the table pressure setting code in the main loop. Note that we will use the dither_flag variable in both the timer interrupt routine and the main loop, so it will have to be a 'global' variable (i.e., accessible from anywhere in the code).
- when the dither pulse is finished, we will clear the dither_flag, and let the pressure control be set in the main loop again.
- the dither_flag variable will be a char (8 bits), but we only really need one bit to indicate on or off. So we will use the first bit for this, and later use the second and third bits to set flags for the refresh on output1 and output2. This will save space in RAM.

These tell us the sort of conditions and variables we will want to use in our code (coming shortly). It's worth noting that we haven't actually written any code yet. But by doing the above, we are ready to write working code when we do start, having taken a number of things into consideration (though more will likely pop-up as we write the code).

One thing we want to do is look at any conditions we might have and decide which order they should be in. In general, we want to exclude the main part of the dither code as early as possible, as that will speed up the code. And where we have an option, we want to execute any high speed conditions first so that we slow the code as little as possible when the CPU is needed most (to process things like the VSS signal). There aren't obvious choices for the ones we use here, but in many cases there will be.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Mon Mar 30, 2009 4:12 pm
by Bernard Fife
Okay, it's finally time to open the compiler!

The first thing will will do is add the flag and counter variables. We will add these before the 'main' routine so they will be global and can be used by any part of the code (this is further documented in the code itself). The declarations are:

Code: Select all

char dither_flag;              // PWM dither and refresh flags
int dither_counter;            // dither counter for zero to dither_int+dither_dur
int refresh_counter;          // refresh counter for output1 and output2 from zero 
                               // to refresh_int+refresh_dur
Note that we are using the same counter for both output1 and output2. This shouldn't matter because they will be called often relative to the on/off cycles of the outputs themselves. If we had unlimited RAM we might separate them.

Then we compile. No errors? Great, we carry on.

Next we need to initialize the counters and flag. We do this near the beginning of the main(void) routine (before the looping starts at for(,,) ) so they are only initialized on start-up or a reset:

Code: Select all

// Initialize PWM Dither and Refresh Variables
dither_flag = 0;          // reset PWM dither and refresh flags
dither_counter = 0;       // dither counter to zero
refresh_counter = 0;      // refresh counter to zero
Compile again. Finally (for this post), we'll put a conditional loop around the pressure control lookup table in the main loop so the pressure control is not changed if the dither_flag is not zero. The existing code for setting the pressure control looks like this:

Code: Select all

/*-- SET_PRESSURE_CONTROL_PWM Duty Cycle ------------------------------------*/
// The Pressure Control solenoid controls main line pressure, pulsed at 
// 0-60% duty cycles at 293Hz.
//
// Pressure control modulation percent varies from 0% at WOT to 60% at idle.
//
// Note that MegaTune uses 100-PC_DC, so:
//  - 60% PC_DC  (lowest pressure) in the code is specified as  40% in MegaTune, 
//  -  0% PC_DC (highest pressure) in the code is specified as 100% in MegaTune. 

// There is no cleaning pulse (dither) every 10 seconds 
// on this transmission (unlike the 4L80E). 

// if gear=neutral, set pressure control PWM% to PCneutral%

if (outpc.manual_gear == 0) { 

  PWMDTY2 =  (unsigned char) ((inpram.PCneutral*PWMPER2)/100);     // set PC PWM duty to PCneutral% in neutral (max bleed - lowest pressure)
                                                                   // default PWMPER2 is 244
  }   // End if  (outpc.current_gear == 0)

if (outpc.manual_gear > 0) // Forward gears, reverse is below
 {   // In gear
  
  // Look-up pressure control solenoid (PC) duty cycle in pc_table[12x12] and set tempPC1
  // as a fractional count of PWMPER2 (tempPC1 is the look-up value, tempPC2 is the 
  // voltage/temperature adjustment, these are added to get the duty cycle count)
  
  tempPC1 = 0; // reinitialize tempPCx
  tempPC2 = 0;
    
  tempPC1 = (uchar)((intrp_2dctable((uint)outpc.speedo/10, (int)outpc.LOAD/10, // in 'tics'
                                    (uchar)NO_MPH, (uchar)NO_LOADS, 
                                    (uint *)&inpram.vss_table[0], 
                                    (int *)&inpram.LOAD_table[0], 
                                    (uchar *)&inpram.pc_table[0][0]) * PWMPER2)/100); 

  // ------------- PC Voltage Correction -------------------------------------------------
  // The voltage diffference is (120-vBatt)/10, since vBatt is x10
  // the correction is the voltage difference multiplied by the 
  // battery correction factor inpram BatFac/10
  // Note that BatFac is negative in MegaTune, but positive in this code.
  // We move the divisors around a bit to avoid over/underflows.
                                                                                                                     
  if (outpc.vBatt < 120)                             // if less than 12.0 Volts, decrease 
                                                     // the DC (increase pressure), 
                                                     // base is 12.0 volts x 10
    {
    tempPC2 = (long) (((120 - outpc.vBatt)*inpram.BatFac)/100); // adjust for battery supply voltage less than 12 volts                                
    tempPC2 = (tempPC2*PWMPER2)/100;                            // and convert to number of PWM tics  

    if ((tempPC1-tempPC2) > 0)                       // make sure result will be positive
      {   
      PWMDTY2 = (uchar)(tempPC1 - (char) tempPC2);   // set the value by subtracting DC (increase pressure)
      } 
    }
  else  // battery voltage greater than 12.0 V
    {
    tempPC2 = (long) (((outpc.vBatt - 120)*inpram.BatFac)/100); // adjust for battery supply voltage greater than 12 volts
    tempPC2 = (tempPC2*PWMPER2)/100;                            // and convert to number of PWM tics

    if ((tempPC1+(char) tempPC2) < PWMPER2)          // make sure we don't try to set PWM DC to greater than 100%
      { 
      PWMDTY2 = (uchar)(tempPC1 + tempPC2);          // set the value by adding DC (decrease pressure) 
      }
    }; // end if (outpc.vBatt <= 120)
                                                                                                                                                    
   tempPC2 = 0; // reinitialize
   
  // ------------- PC Temperature Correction -------------------------------------------------
  // From PCtemp[] table
  // We move the divsors around a bit to avoid over/underflows.
                                                                                                                     
  if (inpram.stdin_cfg & 0x02) tempPC2 = intrp_1dctable(0,outpc.clt,NO_TEMPS,inpram.temp_table,inpram.PCtemp)* PWMPER2/100; // 1-D table lookup
    
  // 1D Interpolation Routine
  // intrp_1dctable(char sgnx, int x, unsigned char n, int * x_table, char * z_table)
  // where: 
  // sgnx     = 
  // x        = the index value for the lookup
  // n		    = the number of rows in (size of) the lookup table
  // x_table  = the name of the lookup table
  // z_table  = the 'looked up' values
  // return the interpolated value as an integer

    if ((PWMDTY2-tempPC2) >= 0)                      // make sure result will be positive
      {  
      PWMDTY2 = (uchar)(PWMDTY2 - tempPC2);          // set the value by subtracting DC (increase pressure)
      }
    else                                             // result less than zero
      {
      PWMDTY2 = 0;                                   // do not let value go negative (or wrap around)
      }                                                                                                                                                   
    }   // End if (outpc.manual_gear > 0)

if (outpc.manual_gear < 0) PWMDTY2 = 0;              // set maximum line pressure in reverse/park (no bleed)
    
// Now convert the PWMDTY2 to line pressure for calcs and datalog.
// we only do this here so that intermediate results of voltage and temperature
// corrections do not show in datalogs.

outpc.PC_duty = (unsigned char)(100-((PWMDTY2*100)/PWMPER2)); // set datalog value (100-value to have datalog value same as PC table)
                                                              // input/table values are 0-60%  - adjusted by MT
Note that there are a number of corrections and adjustments applied. We don't want any of these during a dither pulse. So we surround the entire code snippet above with a condition statement that says if the first bit of dither_flag is on, don't do the loop, if it is zero, then do the loop.

The if statement looks like this:

Code: Select all

if (!(dither_flag & 0x01)) {        // if not (!='not') (8-bit dither_flag ANDed with 00000001)  
  ...
  all the code above
  ...
  }        // end if !dither_flag


Again we compile, and hopefully there are no errors. If so, then we are done the set-up, and can finally add the dither and refresh to the timer interrupt (in the next post).

A few things to note:

- 0x01 is the hexadecimal number one. It is equivalent to the decimal ("base-10") value one, and the binary value one as well. However, this is not the case for other numbers. For example, the hexadecimal number 23 is equivalent to 35 in decimal and 100011 in binary. C can only deal directly with decimal and hexadecimal numbers (you can add other types, at the cost of some overhead). Hexadecimal numbers are prefaced with '0x' in C to distinguish them from decimal numbers. However, it becomes natural to work in hexadecimal numbers after a bit of practice.
- the condition statement (dither_flag & 0x01) has the 'bit-wise operator' &. In this case each bit of dither_flag is compared to the corresponding bit in 0x01, and the resulting bit is true if, and only if, both bits are set to one. In our case, since 0x01 (= 00000001 in binary) is all zeros except the right most bit, this can only have a value if dither_flag also has a one in the right-most position (the 'least significant bit').
- the exclamation mark before the condition means "not" and it inverts the result, so zero becomes one, and vice versa. So our statement overall is "if dither_flag's first bit is not equal to one" do the following code (set the pressure control PWM%).
- it is always a good idea to label the end of the if statements. It's easy to loose track of them as you add code, and this can lead to all sorts of errors (including some that won't be reported by the compiler - your code will just act oddly).
- note that the AND operator & is bit-wise, but && is not. So while we used & above (and will use it again to check the other bits in the flag when we set the refresh pulses), we use && to compare the overall value of two variables (ex. if (ECUtype == 3) {... we have to do something ...}
- finally note that '==' is not the same as '='. We don't use them here, but we will soon.
--> == means if the thing on the right is equal to the thing on the left. It is a conditional statement with a logic result.
--> = means set the value of the variable on the left to be the same as the value of the variable on the right.
So one compares, the other assigns. The compiler may give you a warning of an assignment within a condition, but it may not - so be careful. This is something that is easy to mix up (though you do get used to it in time).

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Tue Mar 31, 2009 8:20 am
by h22
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, that is quite generous of you and I for one would love just that sort of thing. a bit like a Mega-programming-manual. I have been eagerly awaiting the GPIO board not only to get a 4L60E up and running but also to build all manner of embedded controllers. A quick referance guide for the non-programmer type as you discibe would be extreamly helpfull.

Re: Can the GPIO run variable cam phasing?

Posted: Tue Mar 31, 2009 11:34 am
by Bernard Fife
h22,

Sounds good. I will complete the current PWM example (both with the timer output and bit-banging the outputs), then do very explicit guides on the remaining mods to the code. This will likely include doing a high frequency speedo output, an input shaft speed sensor function (using a timer channel), a separate OD shift switch (which will get into how to configure and us general purpose inputs/outputs), and whatever else comes up.

At some point this will all go into a web page to make it as easy as possible for people to see how things are done.

Also, starting with the next post, I will attach the main.c file so people can look at the 'big picture' if they wish.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Fri Apr 03, 2009 11:50 am
by Bernard Fife
Now that we have the variables aet up, we can start work on the code to do the dither pulse on the pressure control output.

This will go in the millisecond section of the ISR_Timer_Clock(void) interrupt handler. This interrupt routine is called by the processor's internal clock every 0.128 milliseconds. Every eighth call we add one to the milliseconds count. One the seven 'tics' (interrupt calls) leading up to the eight, we skip most of the interrupt code with a GOTO CLK_DONE; statement. (The ISR also has section that uses multiples of the millisecond clock to set 0.010 and 1.000 timers for various purposes).

Code: Select all

INTERRUPT void ISR_Timer_Clock(void)  {
  short ix;
  unsigned long tmp,tmp1,tmp2;
  
  // .128 ms clock interrupt - clear flag immediately to resume count
  CRGFLG = 0x80;  // clear RTI interrupt flag
  // also generate 1.024 ms, .10035 sec and 1.0035 sec clocks
  lmms++;         // free running clock(.128 ms tics) good for ~ 110 hrs
  can_mmsclk++;
  mms++;          // in .128 ms tics - reset every 8 tics = 1.024 ms
      
  // check mms to generate other clocks
  if(mms < 8)goto CLK_DONE;
  mms = 0;
  millisec++;     // actually 1.024 ms, for seconds count
....
In the millisecond section we need to add a section of 'dither' code that will:

-> increment the counter
-> if the dither_counter is has exceeded inpram.dither_intPC then:
-- if the conditions are right (auto_mode, etc.)
---- set the dither_flag first bit to one
---- set PWMDTY2 = PWMPER2
-> if the dither_counter is greater than inpram.dither_intPC + inpram.dither_durPC then:
--- reset the dither counter to zero
--- reset the dither_flag first bit to zero

Note that we won't check if the dither_counter is less than inpram.dither_intPC because this is the 'do nothing' situation anyhow (i.e. we just want the main loop to continue to set the pressure control PWM as usual).

The code looks like this:

Code: Select all

  // Pressure Control Output Dither Pulse
  dither_counter++; // increment the counter
  if (dither_counter > inpram.dither_intPC)
    {
    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 75))
      {
       dither_flag |= 0x01;  // set the dither_flag first bit to one
       PWMDTY2 = PWMPER2;    // set duty cycle to 100%
      }
    }  // end if dither_counter ...
  if (dither_counter > (inpram.dither_intPC + inpram.dither_durPC))
    {
     dither_counter = 0;   // reset the dither counter to zero
     dither_flag &= ~0x01; // reset the dither_flag first bit to zero 
    }  // end if dither_counter ...
Compile it, and it should work, assuming we haven't mis-spelled variable names, omitted brackets, etc. All of this took much longer to type into this thread than to actually do on the code, and adding something like this is usually a 1 or 2 total hour job (including setting up the INI , etc.) at the most. So adding features is easy.

However we are NOT done yet. Just because the code will compile without error, doesn't mean it works like we want it too, or that it hasn't messed something else up. So our next task is to debug - and we can only really do that by testing extensively. We have to test that the modification we wrote works as intended under as many conditions as we can come up with, AND that it hasn't affected other parts of the code adversely. This will usually take many times longer than adding the code, and is the only way to get code that works properly. More on this shortly.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Fri Apr 03, 2009 12:03 pm
by Bernard Fife
Some comments on the code:

Code: Select all

if (outpc.auto_mode && ...
Note that this is saying if outpc.auto_mode exists = i.e., if it is anything but zero (since a value of 0 is the same as 'false' in a conditional statement). Since zero is manual mode, and anything higher is one of the auto modes, we have an easy way of checking. The other conditions are checking that we are not shifting and that we are not at high average loads. We AND them so that any one condition can disqualify the dither pulses.

Code: Select all

dither_flag |= 0x01; 
The above is the same as dither_flag = dither_flag | 0x01; '|' is the symbol for a bit-wise 'OR'. It is comparing each of the bits in dither_flag to the corresponding bit in the binary value 00000001. If either of them is one (or both), then the value is set to one in the result that is assigned to dither_flag. Since there is a one in the first bit of the value 00000001, the first bit of dither_flag will be set to one. All of the other bit values will remain unchanged (0 or 0 is zero, zero or 1 is one).

Code: Select all

dither_flag &= ~0x01;
This is similar to the above, except it is a bit-wise AND, and the 'compliment' of the binary value is taken with the compliment operator ~ (the 'tilde'). A compliment is a binary number where all the ones are changed to zeros, and all the zeros are changed to ones. So ~00000001 is 11111110. A bit-wise AND means that corresponding bits must both be one for the result to be one. Since the first bit of 11111110 is zero, the first bit of dither_flag is set to zero (and the rest are unchanged).

It is worth studying the above examples to really understand them, since this is exactly the form we will use to turn off and on many outputs (this is 'bit-banging').

I will include the main.c source file for people to look at.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Sat Apr 04, 2009 11:38 am
by Bernard Fife
To start debugging the code we just added, we could take a number of approaches. The most certain way to be sure to see if we are getting what we want is with an oscilloscope, and we will do that eventually. Before that, however, we'll use the outpc.dbug variable to see if the code is performing as we expect (and this may be the only pre-vehicle testing we could do if we didn't have a oscilloscope).

If we wanted, we could have used an LED to indicate the dither status (turn it off for all normal functions, turn it on while the dither_pulse is active). However, this is more involved, and doesn't tell us the timing (we could use a stop watch, etc.). The biggest problem, though, is it wouldn't work very well for faster pulses of the sort we'll see in the refresh pulse for the other outputs. So we'll demonstrate the outpc.dbug method.

The first thing we do is change the conditions to those we can easily simulate on the bench. In this case, that means setting the LOAD_long threshold to 110 from 75. That way the dither pulse is active unless we blow into the MAP sensor hose. (Note that when you change something like this, a good idea is to comment out the original line of code with double forward slashes, then copy and paste the original line below to edit. This makes it easier to revert, and is a pretty good reminder that you have changed something you may want to change back!)

Code: Select all

//    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 75))
    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 110))
Next, we'll add the outpc.dbug variable. This first thing is to disable it's current use as the VSS error counter at line 2472:

Code: Select all

outpc.dbug = VSS_error; // set dbug to VSS error count unless used for debugging
becomes

Code: Select all

// outpc.dbug = VSS_error; // set dbug to VSS error count unless used for debugging
We need to distinguish between normal operation and the dither pulse. outpc.dbug is an unsigned int (i.e. it runs from 0 to 65535 or 0 to 65.535 seconds). Since our typical dither pulse interval runs around 10 seconds with a duration of one second, what we will do its let the counter stay static during the interval, then run from 0 to 1000 for the first dither pulse inteself. So what we expect to see is outpc.dbug sit at zero for about 10 seconds, then count up from 0 to 1000, then stay there for ten seconds, then from 1000 to 2000, etc. (because we have not reset the outpc.dbug value anywhere).

To do this, we insert a outpc.dbug assignment statement:

Code: Select all

  // Pressure Control Output Dither Pulse
  dither_counter++; // increment the counter
  if (dither_counter > inpram.dither_intPC)
    {
//    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 750))
    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 1100))
      {
       dither_flag |= 0x01;  // set the dither_flag first bit to one
       PWMDTY2 = PWMPER2;    // set duty cycle to 100%
       outpc.dbug++; // DEBUG
      }
    }  // end if dither_counter ...
  if (dither_counter > (inpram.dither_intPC + inpram.dither_durPC))
    {
     dither_counter = 0;   // reset the dither counter to zero
     dither_flag &= ~0x01; // reset the dither_flag first bit to zero
    }  // end if dither_counter ...
Note that we have added the comment // DEBUG after the addition to make it easier to find and remove all of these later (it's trivial in this case, but isn't always so easy!)

We compile (hopefully without warnings or errors, load the code, and open MegaTune). Open the x_debug gauge (by right clicking on any gauge, etc.). All of the debugging gauges are prefaced with 'x_' in the MShift code/INI gauge listing.

You'll see the gauge doesn't do much. This is because we have forgotten that the MAP units are x10, so if we want a limit of 75 kPa we need a value of 750 (and 1100 for 110 kPa, etc.). So we change that and re-compile. This works mostly as expected, but counts to 1001, 2002, 3003, etc. instead of multiple of 1000. To fix that, we change:

Code: Select all

  if (dither_counter > (inpram.dither_intPC + inpram.dither_durPC))
to

Code: Select all

  if (dither_counter > (inpram.dither_intPC + inpram.dither_durPC - 1))
Recompile and check and all is well. The next step would be to move the outpc.dbug statement above into the interval condition to verify that it is working exactly as predicted with the default values (or alternately - look at the datalog values and compare the dither timing to the clock). Then we can go on to check different settings for the interval and duration (including zeros!), verify that it doesn't dither during manual mode or at higher loads, check the main loop frequency (using the gauge x_loop_Hz), etc. Finally we look at the output on the oscilloscope (with the output pulling a load so the oscilloscope has a changing voltage to work with).

When making mods like this, it's always a good idea to keep an eye out for things that may not be quite right from earlier mods. In this case, the CLK_DONE: statement (for the GOTO) was in the wrong place, and slowing the code down a bit more than it needed too - so it was fixed.

Once we are satisfied the code works properly under all conditions, we remove the 'outpc.dbug++; // DEBUG' statement, change the condition back to 750 (== 75 kPa) and remove the forward slashes we added earlier to the 'outpc.dbug = VSS_error;' statement. Compile, load and run the code once more to make sure we haven't messed anything up. If that's good, it's over to the oscilloscope (or out to the vehicle) to check some more!

Three things help make developing code easier:
- a bench set-up with full hardware. In my case, this is a MS-II/V3/V2.1 stim plus a GPIO and its own stim.
- a push-button switch on the bootloader of the bench GPIO, so serial monitor ("bootloader") mode can be entered just by pushing the button while powering up,
- Codewarrior configured to open the downloader and MegaTune. I have these on F11 and F12. So I can compile, load and test code with just F7/F11/F12. You define the programs you want to open with what Fx keys under 'Edit/Commands and Key Bindings' in CW.

Lance.

Re: Can the GPIO run variable cam phasing?

Posted: Sat Apr 04, 2009 9:35 pm
by Bernard Fife
The next thing we want to do is 'bit-bang' to PWM the two outputs PE4 and PM2.

We start with the statement in the ISR_timer_clock interrupt routine:

if(mms < 8)goto CLK_DONE;

mms is in .128 ms tics and is reset every 8 tics = 1.024 milliseconds. We'll want to have a series of statements that execute every 0.128 milliseconds (7812 times a second), so we can add as many statements as we like after the CLK_DONE: label, and they will execute every 0.128 milliseconds (if we put anything before the CLK_DONE: label, it would only execute once per millisecond).

To PWM we need at least one on and one off 'tic'. So the shortest period we can have is 2*0.128 = 0.256 for a frequency of 1/0.000256 = 3906 Hertz. However, we only have the possibility of 50% PWM. With 4 tics, we can have 25%, 50%, or 75% at a frequency of 1/(4*0.000128) = 1953 Hz. Other combinations are:

total
tics --- freq. --- PWM% possibilities
2 --- 3906 --- 50%
3 --- 2604 --- 33%, 67%
4 --- 1953 --- 25%, 50%, 75%
5 --- 1562 --- 20%, 40%, 60%, 80%
6 --- 1302 --- 17%, 33%, 50%, 67%, 83%
7 --- 1116 --- 14%, 29%, 43%, 57%, 71%, 86%
8 --- 977 --- 13%, 25%, 38%, 50%, 63%, 75%, 88%
9 --- 868 --- 11%, 22%, 33%, 44%, 56%, 67%, 78%, 89%
10 --- 781 --- 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90%
...
100 --- 78 --- 1% increments
...

We'll start by setting up the variables. We'll set one for the 'on' duration, and one for the 'off' duration, and arbitrarily call them: out12_pwm_on and out12_pwm_off. (Note that we could have chosen the frequency and PWM duty cycle instead, or some other values, but we'll soon see that these are the easiest to work with directly - giving the processor less to do and not requiring a reboot if we change the values.) If we choose these to be unsigned chars, the values can range up to 255, so the lowest PWM frequency will be 1/((255+255)*0.000128) = 15 Hertz (at 50% duty cycle). So the range will be 15 to 3906 Hertz. We need to add these to the inpram structure so that MegaTune can 'see' and change them. Also, in the INI we'll want to set up the variables as milliseconds rather than clock 'tics'. To do this, we will set the scaling factor to 0.128 and allow three decimal places. We will also define that if the out12_pwm_off == 0, then we won't do PWM (regardless of the value of out12_pwm_on). MegaTune will then only allow valid combinations to be sent to the processor.

To toggle the pins on and off, we use an eXclusive OR (aka. XOR). The operator is ^, and it is a 'bit-wise' operator. This means that each corresponding bit value in the result is set to one if (and only if) exactly one of the two corresponding bits being compared is one.

For example:
0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;
1 ^ 1 = 0;

The statement we use to toggle the 4th bit of port E (PE4) is:

PORTE ^= 0x10; // which is the same as PORTE = PORTE ^ 0x10;

In this case, the 0x10 = 00010000, and when it is XOR'd with the PORTE register all the values stay the same (XOR with zero doesn't change anything) EXCEPT the forth bit which is set to 1 if the 4th bit of PORTE is 0, and set to 0 if the 4th bit of PORTE is 1 - i.e., it toggles the bit exactly like we want it to for PWM!

The corresponding statement for PM2 is:

*pPTMpin[2] ^= 0x04;

Also, note that everywhere else in the code we have previously turned the non-PWM output1 (PE4) on, we have set the value of outpc.solA to one. And when we turn PE4 off, we set outpc.solA to zero. So that means we only want to toggle the PE4 output if outpc.solA == 1. That is, we don't want to PWM the pin if it supposed to be off. Similarly, everywhere else in the code we have turned the non-PWM output2 (PM2) on, we have set the value of outpc.solB to one. And when we turn PM2 off, we set outpc.solB to zero. So that means we only want to toggle the PM2 output if outpc.solB == 1

Now that we have thought things through, we can think about what the code should look like (next post).

Lance.