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).
Bernard Fife
Posts: 1696
Joined: Fri Apr 04, 2008 1:28 pm

Re: Can the GPIO run variable cam phasing?

Post by Bernard Fife »

Okay, now that we have the initial thoughts out of the way, we'll add the variables to the INI and code, and sketch out the 'pseudo-code'. (Pseudo-code is term term meant to imply that the code is logically correct, but may not have all the proper syntax, etc. to become running code. It is used as a guide when writing the functional code.)

In the global variables section, under 'int refresh_counter;' we added earlier, we add:

Code: Select all

unsigned int out12_pwm_count;  // output1 and output2 counter (in 0.128 clock tics)
This adds the output1/2 PWM counter. This doesn't need to be added to the INI, it is entirely internal (but note that we can view it by setting outpc.dbug = out12_pwm_count;).

However, we also need to add the PWM parameters to the code and INI. In the code, the parameters are add to the inpram. structure so MegaTune can see them:

Code: Select all

typedef struct {
...
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;
becomes:

Code: Select all

typedef struct {
...
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
unsigned char out12_pwm_off;    // output1 and output2 off duration (in 0.128 clock tics)
unsigned char out12_pwm_on;     // output1 and output2 on duration (in 0.128 clock tics)

} inputs1;
we also need to add the default values:

Code: Select all

50,          // PWM refresh interval (msec)
8,           // PWM refresh duration (msec)
10000,       // PC dither interval (msec)
1000,        // PC dither duration (msec)
0,           // output1 and output2 off duration (in 0.128 clock tics)
100,         // output1 and output2 on duration (in 0.128 clock tics)
};
Then we add these to the INI:

Code: Select all

  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 *********************
;     pageSize =  544 + 2 = 546 (= 222 hex) bytes
becomes:

Code: Select all

  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
  out12_pwm_off         = scalar,  U08,   546,                "msec",      0.128,   0.0000,        0,  32.64,        3    ; output1 and output2 off duration (in 0.128 clock tics)
  out12_pwm_on         = scalar,  U08,   547,                "msec",      0.128,   0.0000,        0,  32.64,        3    ; output1 and output2 on duration (in 0.128 clock tics)

; ****************************************************************** end inpram *********************
;     pageSize =  547 + 1 = 548 (= 224 hex) bytes
and change the page size to match:

Code: Select all

   pageSize            = 548,            30
Finally, we add the parameters to a menu:

Code: Select all

   dialog = PWMSettings, "Solenoid PWM Setup"
      field = "TCC PWM Period",           TCC_PWM_Pd
      field = "PC PWM Period",            PC_PWM_Pd
      field = "PC Voltage Correction",    BatFac
      field = "Output3 PWM Period",       SOL32_PWM_Pd
      field = "Output1/2 ON Period",      out12_pwm_on
      field = "Output1/2 OFF Period",     out12_pwm_off
Compile the code, load it, and open MegaTune. If all that happens without errors, we are ready to write some (pseudo) code! We'll do that in the next post.

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 »

Okay, now to look at the pseudo-code that will go in the 0.128 part of the timer interrupt.

For each output (solA and solB) we want to PWM if:

- the 'off' parameter is not zero AND
- the refresh pulse isn't happening AND
- the solenoid is active (i.e. 'on')

We also want to increment the counter on each 'tic' and reset it to zero if it reaches the total number of tics in the user parameters. We will check the 'off' and solenoid refresh parameters first since they apply to both solenoids. Then we check to see if the sol are individually active. So this might look something like this:

Code: Select all

if (inpram.out12_pwm_off && (outpc.dither_flag & 0x02)) then
  {
   out12_pwm_count++; // increment counter

   // Toggle solA (aka. output1)
   if (outpc.solA && (out12_pwm_count < out12_pwm_off)) then     // counter below out_pwm_off value
       PORTE |= 0x10; // if solA is active, set PE4 high
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       PORTE &= ~0x10; // set PE4 low
   end if

   // Toggle solB (aka. output2)
   if (outpc.solA && (out12_pwm_count < out12_pwm_off)) then     // counter below out_pwm_off value
       *pPTMpin[2] |= 0x04; // if solB is active, set PM2 high
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       *pPTMpin[2] &= ~0x04; // set PM2 low
   end if
   
   if (out12_pwm_count >= (out12_pwm_off+out12_pwm_on)) then out12_pwm_count =0; // reset counter when total is reached 
  }
So, if the PWM is off, we stop executing this bit of code. If the dither flag's second bit is on, we stop executing this bit of code (note that we haven't set this bit anywhere in the code - we will have to remember to do this when we write that part of the refresh code). Hopefully this helps make the code run faster. Then if each solenoid output is active, and the counter is low enough, we turn it on, otherwise we set it low. We start the counter with a 'high' period to maximize the response time of the outputs.

Note that we didn't end up using the 'toggle' XOR here, because it would have toggled every 0.128 milliseconds - instead we used the counter and explicit on/off statements. This is often the case - we have to adjust our thinking as we proceed to use what works best - rather than forcing a solution that is awkward. (We could have only toggled when the exact counter values were reached, and this would reduce the number of port 'bit-banging' operations, but you would have to write the code and test to see if this made any significant difference. I generally prefer to test using 'greater than' and 'less than' as there is less chance of missing something (i.e., if the counter exceeds the expect value without actually ever equalling it, the action is still taken))

In the next post we will write and compile the actual code based on the above.

BTW, I have checked the latest MShift code, and it compiles fine within the limits of the free 'special edition' of Codewarrior.

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 »

The actual code ends up looking like this:

Code: Select all

CLK_DONE:  

// Bit-bang PWM on output1 (PE4) and output2 (PM2)

if (inpram.out12_pwm_off && (dither_flag & 0x02))
  {
   out12_pwm_count++; // increment counter

   // Toggle solA (aka. output1)
   if (SOLAst && (out12_pwm_count < inpram.out12_pwm_off))    // counter below out_pwm_off value
       {
        PORTE |= 0x10; // if solA is active, set PE4 high
       }
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       {
        PORTE &= ~0x10; // set PE4 low
       }    // end if  (SOLAst && ...

   // Toggle solB (aka. output2)
   if (SOLBst && (out12_pwm_count < inpram.out12_pwm_off))     // counter below out_pwm_off value
       {
        *pPTMpin[2] |= 0x04; // if solB is active, set PM2 high
       }
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       {
        *pPTMpin[2] &= ~0x04; // set PM2 low
       }    // end if  (SOLBst && ...
   
   if (out12_pwm_count >= (inpram.out12_pwm_off + inpram.out12_pwm_on)) out12_pwm_count =0; // reset counter when total is reached
  }
It is placed below the 'CLK_DONE: label as shown.

A few notes:
- solA and solB weren't the right variables, it was actually SOLAst ("solA state") and SOLBst, and these are not part of the outpc. structure.
- the input parameters needed the inpram. suffix,
- C doesn't use the 'then' or 'end if' parts of the statements,
- note that the brackets for the conditional statements aren't strictly necessary, as there is only one statement each. However, using brackets allows us to comment the if condition and the resultant executed statement(s) separately, making the logic clearer (it also allows us to add statements more easily later.)

In the next post will will do a bit of testing. After that, we can move on to adding the refresh pulse to these outputs.

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 »

Now we need to see if the code actually works, and debug it if it doesn't.

We start by setting the VSS and MAP on the stim so that SolA and/or SolB are on. We'll start by using outpc.dbug to see what the highest value the counter reaches by adding the following line just before the reset statement:

Code: Select all

   if (out12_pwm_count > outpc.dbug)  outpc.dbug = out12_pwm_count; // DEBUG
(we also have to disable any other uses of the dbug variable, outpc.dbug = VSS_error;, as above)

Recompile, load the code, and open MegaTune, and we find the counter sits at zero. It doesn't work. That's not unusual for a new bit of code, and we need to start 'debugging'. So we will check to see if any of the statements after CLK_DONE: label are being performed by putting in a outpc.dbug++; statement right after the label (after commenting out the previous outpc.dbug assignment). The statement climbs to 65535 and back to zero, so getting to the code after the CLK_DONE: label isn't the problem.

It seems that one or more of our conditions are the problem. We'll put the dbug counter inside the first conditional statement brackets and see if this bit of code is ever activated (after changing the default parameters to 100 and 100 so that the PWM is in effect - we could do this in MegaTune, but we'd have to do it each time...). The counter stays at zero. So we are not making it past the if (inpram.out12_pwm_off && (dither_flag & 0x02)) condition.

Looking at this, it's clear why. The dither_flag & 0x02 condition is true only if the dither_flag's second bit is set, but we have never set it. In fact, we want to PWM if the dither _flag second bit is NOT set, so we change the statement to:

Code: Select all

if (inpram.out12_pwm_off && !(dither_flag & 0x02))
The exclamation mark means NOT whatever follows, in C. Now our condition counter works as expected. So lets restore the first debug statement added before the rest and see how that works now. It works almost as expected, counting to 201. so we change the condition to:

Code: Select all

   if (out12_pwm_count > (inpram.out12_pwm_off + inpram.out12_pwm_on - 1)) out12_pwm_count = 0; // reset counter when total is reached
That seems to work. We could go on probing with the outpc.dbug variable, but instead will use an oscilloscope to check the output patterns as we enter various parameters. With the temporary default values of 100 and 100, this is (100+100)*0.128 msec, or 39 Hz. Luckily, this is what we get on a oscilloscope (we could also have used a DMM if it measures frequency - some better one do). In MegaTune lets change the values to 1.28 on and 6.40 off. This should give us a 16.7% PWM at just over 130 Hz. Luckily, this is what we find (see the attached scope shot). We could (and should) go on to test lots of different combinations, and verify that the WM does not operate when the outputs are supposed to be off, etc. That will occupy several hours, and is really necessary if you want things to work right.

The 'final' code is:

Code: Select all

CLK_DONE:  

// Bit-bang PWM on output1 (PE4) and output2 (PM2)

if (inpram.out12_pwm_off && !(dither_flag & 0x02))
  {
   out12_pwm_count++; // increment counter
   
   // Toggle solA (aka. output1)
   if (SOLAst && (out12_pwm_count < inpram.out12_pwm_off))    // counter below out_pwm_off value
       {
        PORTE |= 0x10; // if solA is active, set PE4 high
       }
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       {
        PORTE &= ~0x10; // set PE4 low
       }    // end if  (SOLAst && ...

   // Toggle solB (aka. output2)
   if (SOLBst && (out12_pwm_count < inpram.out12_pwm_off))     // counter below out_pwm_off value
       {
        *pPTMpin[2] |= 0x04; // if solB is active, set PM2 high
       }
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       {
        *pPTMpin[2] &= ~0x04; // set PM2 low
       }    // end if  (SOLBst && ...

   if (out12_pwm_count > (inpram.out12_pwm_off + inpram.out12_pwm_on - 1)) out12_pwm_count = 0; // reset counter when total is 

reached
  }  // end if (inpram.out12_pwm_off ...
Next we will look at adding the refresh pulse to the PWM we have just created.

Lance.
Attachments
Shot of oscilloscope pattern at 1.28 and 6.40.
Shot of oscilloscope pattern at 1.28 and 6.40.
PWM_output12.jpg (38.35 KiB) Viewed 21005 times
"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 »

The code above looks good, but in further testing there was a issue. If you look at the scope trace, it's high for about 17% of the time, and low for the rest. We specified a 17% duty cycle (1.28 on, 6.40 off), so at first glance this looks correct. However, a high voltage on a scope trace when hooked to a transistor's collector means no current is flowing, and a low voltage means current is flowing. So we have things backwards. The problem turns out to be the conditions are backwards for the pin setting assignments we made. We want the pin set high when the counter is in the 'on' range, not the 'off' range. So we change the code to:

Code: Select all

CLK_DONE:  

// Bit-bang PWM on output1 (PE4) and output2 (PM2)

if (inpram.out12_pwm_off && !(dither_flag & 0x02))
  {
   out12_pwm_count++; // increment PWM counter
   
   // Toggle solA (aka. output1)
   if (SOLAst && (out12_pwm_count < inpram.out12_pwm_on))    // counter below out_pwm_on value (start with on portion of cycle)
       {
        PORTE |= 0x10; // if solA is active, set PE4 high
       }
   else     // counter between out_pwm_off and (out_pwm_off+out_pwm_on)
       {
        PORTE &= ~0x10;  // set PE4 low
       }    // end if  (SOLAst && ...

   // Toggle solB (aka. output2)
   if (SOLBst && (out12_pwm_count < inpram.out12_pwm_on))     // counter below out_pwm_off value
       {
        *pPTMpin[2] |= 0x04; // if solB is active, set PM2 high
       }
   else     // counter between out_pwm_on and (out_pwm_off+out_pwm_on)
       {
        *pPTMpin[2] &= ~0x04; // set PM2 low
       }    // end if  (SOLBst && ...

   if (out12_pwm_count > (inpram.out12_pwm_off + inpram.out12_pwm_on - 1)) out12_pwm_count = 0; // reset counter when total is reached
  }  // end if (inpram.out12_pwm_off ...
and all is well! It just goes to show that you MUST test things thoroughly. Also, the pin settings would be reversed if a PNP output transistor was used, so you have to check the hardware - not just the processor pins. In the case of the GPIO, an NPN TIP120 is used, and the above code works.

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 »

Now that we have made the output 1 & output2 into PWM outputs, we are going to add a 'refresh' cycle to the outputs. This will hold the PWM output high for a certain duration at specified intervals. This will be very much like the 'dither' pulses we added earlier to the pressure control output. In fact, it's similar enough that rather than write code from scratch, we will adapt that bit of code (that we wrote earlier in this thread):

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))
      {
       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 - 1))
    {
     dither_counter = 0;   // reset the dither counter to zero
     dither_flag &= ~0x01; // reset the dither_flag first bit to zero
    }  // end if dither_counter ...
We need to change a few things, obviously. To start with, instead of setting PWMDTY2 to 100%, we need to switch solA and solB on, which is done with:

Code: Select all

PORTE |= 0x10; // set PE4 high
*pPTMpin[2] |= 0x04; // set PM2 high
We will use a different counter, of course: refresh_counter, and different input parameters: refresh_int and refresh_dur; we defined these earlier.

We also need to toggle different bit in the dither_flag variable (bits 2 and 3 instead of the first one). This would be done with:

Code: Select all

dither_flag |= 0x06;  // set the dither_flag 2bnd & 3rd bits to one - 0x06 == 00000110
We won't have any load, mode, or shift conditions on the outputs - if the output is on, we want consistent PWM operation to the solenoid.

Finally, we only want to set the value high (i.e. refresh the PWM) for solA if solA is active. And the same for solB. Luckily, we have already defined variables for this: SOLAst and SOLBst. So we will only set the output high if SOLAst <> 0

So out first guess at the code would be something like:

Code: Select all

  // Output1 and Output 2 Refresh Pulse
  refresh_counter++; // increment the counter
  if (refresh_counter > inpram.refresh_int)
    { // we handle the outputs separately, since they won't both necessarily be on at the same time
    if (SOLAst) // if output1 (solA) is active
      {
       dither_flag |= 0x02;  // set the dither_flag second bit to one
       PORTE |= 0x10; // set PE4 high
      }
    if (SOLBst) // if output2 (solB) is active
      {
       dither_flag |= 0x04;  // set the dither_flag third bit to one
      *pPTMpin[2] |= 0x04; // set PM2 high
      }
    }  // end if refresh_counter ...
  if (refresh_counter > (inpram.refresh_int + inpram.refresh_dur - 1))
    {
     refresh_counter = 0;   // reset the refresh counter to zero
     dither_flag &= ~0x06; // reset the dither_flag second and third bits to zero
    }  // end if refresh_counter ...
This goes immediately after the dither pulse code in the millisecond section of the timer interrupt. We compile that, and if all goes well, we start testing (next post).

BTW, it is very common to re-use and adapt code in this way. It can save a lot of time and effort. If the code is well commented and clearly laid out (and especially if you were the one that wrote it in the first place), it becomes even simpler. The above code took about 3 minutes to adapt and compile (it took longer to write this post!).

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 bit-banging the PWM is so easy, why do we ever bother to do it any other way?

There are a few answers to this:

- using a timer channel (PWMPERx and PWMDTYx) is much more accurate (because it isn't affected nearly as much by other interuppts running, etc.), and this is especially noticeable the higher the PWM frequency required,

- the timer PWM has a much larger range (if we manipulate the timer clock frequency, etc.),

- it is easier to code and harder to mis-code. There are only two (or maybe three parameters if you are altering the clock frequency) to manipulate, and their purpose and usage is clear.

All of these result from the fact that PWM via timer channels is done in hardware, while bit-banging is done in software.

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

Re: Can the GPIO run variable cam phasing?

Post by slyrye »

Hello Lance
Any chance we can see mappable pwm's on msgpio? :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 »

slyrye,

That depends on what you mean by "mappable PWM".

The trans code has up to 4 PWM'd spare ports (up to 4 in the 5.00x beta code, up to 3 in the 4.1xx release code) in addition to the TCC and solenoid PWM'd outputs. They are outputs 4, 7, 8, and 9. When these outputs are used as spare ports, they can have the PWM percentage determined in real time by either a 1x12 PWM table with a single look-up index, or a 9x12 PWM table with two indices. The indices can be selected by the user from load, speed, RPM, temperature, current gear, MAP, or TPS. There is more on this here: http://www.msgpio.com/manuals/mshift/spareport.html

If that is what you mean, then yes, it is obviously possible.

If you mean swapping the timer PWM processor pins to other circuits on the GPIO board (or add-on boards), that is what the 25x2 header is there for.

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

Re: Can the GPIO run variable cam phasing?

Post by slyrye »

Awesomely magnificent...! I'll give it a try on open loop vvti continuous engagement, I am an experienced pic mcu programmer but not so confident on programming freescale yet...! Can the code provide variable index as well sir? I hope to give the community a help in the future as well :D
Post Reply