Slow Duty Cycle Increase w/ PIC

Discussion in 'Programmer's Corner' started by jwilk13, Jul 6, 2011.

  1. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Hello all,

    I'm fairly new to PIC programming, and I'm working on a project dealing with PWM (gasp! another PWM post). Here are the details:

    I'm using a PIC18F45K20 (the demo board with the PICKit3), so I'm using the built-in PWM function. The frequency and other parameters are established as you would expect, and the duty cycle is controlled with the external potentiometer on the demo board. Essentially, an analog voltage is established at RA0 (AN0) and converted with the ADC, compared, and the duty cycle is set. I've also implemented two other funcitons that aren't really important, but they basically set the max duty cycle (with an external trimpot) and the minimum duty cycle (with another external trimpot).

    My question is as follows: Is there a way anyone can think of to gradually increase the duty cycle of the PWM signal with an abrupt change to the voltage applied to RA0 based on an external analog input? In other words, I don't want the duty cycle to go from 0% to 50% instantly, I want it to slowly ramp up to it. However, I would like to be able to change this "ramp" time based on a 3.3V analog input (lower voltage, slower ramp; higher voltage, faster ramp).

    My first thought was to use a for loop that ramps up the value in the register that controls duty cycle slowly based on the external input. This is the code I wrote for that (unfortunately it doesn't work...I think it's something to do with the numbuf array):

    #include <system.h>
    #include <float.h>
    #include <stdlib.h>
    #include <stdio.h>

    unsigned int ADC_ramp()
    {
    unsigned int ramp0;
    adcon0 = 0b00110001;
    set_bit(adcon0,1);
    while (adcon0.1);

    ramp0 = adresl;
    return ramp0 += (adresh << 8);
    }

    unsigned int ADC_duty()
    {
    unsigned int duty0;

    adcon0 = 0b00000001;

    set_bit(adcon0,1);
    while (adcon0.1);

    duty0 = adresl; // Get the 8 bit LSB result
    return duty0 += (adresh << 8); // Get the 2 bit MSB result

    }

    void main()
    {
    unsigned int ramp;
    unsigned int duty;
    unsigned int ramp_delay;

    adcon2 = 0b10111101;
    adcon1 = 0b00000000;
    trisa = 0b00000111;
    porta = 0;
    ansel = 0b00000111;
    trisc = 0;
    portc = 0;
    osccon = 0b11010111;
    pr2 = 0b11111001;
    t2con = 0b00000111;
    trisb = 0b00000001;
    portb = 0;
    anselh = 0b00010000;



    ramp = ADC_ramp();
    duty = ADC_duty();

    for (int i = 0; i <= duty; ++i) {
    char numbuf [2];
    itoa (i, numbuf, 2);
    ramp_delay = ramp*2;
    // return numbuf;

    delay_ms(ramp_delay);

    }

    }

    The first two functions read the ADC for the duty cycle and ramp values. The ramp function is implemented in main. Like I said, I'm new, so please take it easy on me :). I'm going to try playing around with the ADC time now and see if I can make any progress there. Even just pointers would be greatly appreciated (I'm not looking for someone to write my code for me). Thanks in advance.
     
  2. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Also, the code above is not the actual code for the project. It is simply a test for the ramp timer portion. In my full code, the PWM and ADC are working properly and I understand them well.
     
  3. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Just an update:

    I implemented a for loop that looks something like this :

    for (i=0; i < duty1; i++){
    int ramp_delay0 = (ramp/250);
    delay_ms(ramp_delay0);
    duty = i;

    It's in the right direction because it is pausing when I try to increase the duty cycle. The only problem is it's jumping from say 20% to 80% (with the pause) whereas I want it to slowly increase to 80%.

    Any hints would be appreciated...I'm gonna keep chugging along.
     
  4. MCrowe

    Member

    May 29, 2011
    69
    0
    Im probably not the best person to be answering considering my first foray into pics was only a few weeks ago. BUT, how about if you had a variable that stored the value from the ADC read, so on the next round of updating the duty cycle, it compares the stored variable from last time with the new ADC value, if the new ADC is greater than the stored value it ADDS your ramp speed from you ADC to the stored value and uses that for the Duty cycle. So every time the interrupt/ subroutine is called that sets your duty cycle it only ever adds or subtracts the small number to it. Make sense?
     
  5. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,395
    1,607
    Make the design first, then start into coding the design.

    What I see you saying is you have two analog inputs, duty cycle and change rate. There is one output, the PWM duty cycle. What I do not see is how the "gradually increase the duty cycle of the PWM signal" should work. At what rate of change will this increase take place?

    Additionally, as soon as we are talking "rate of change" you need to add some sort of timing element into your code. For now you could do this as a simple delay within a loop. Then use the change rate to determine how many delays you wait until you change the duty cycle.

    Here's some pseudo code:

    Code ( (Unknown Language)):
    1.  
    2. [FONT=Courier New]unsigned int ramp = 0;  // used to calculate our rate of change
    3. unsigned int ADC_Dw;    // raw A2D reading for final Dw
    4. unsigned int ADC_ramp;  // raw A2D reading rate of change
    5. unsigned int PWM_Dw;    // current calculation of Dw
    6.  
    7. while(1)[/FONT] [FONT=Courier New]
    8. {
    9.     delayMs(SomeNiceValue); // this delay sets our max rate of change (resolution)
    10.     ReadA2D(ADC_Dw);        // get fresh Dw
    11.     ReadA2D(ADC_ramp);      // get fresh ramp final setting
    12.     if (ADC_ramp != PWM_Dw)
    13.     {
    14.         // our current pot setting does not match what the PWM is set for
    15.         // so we have to adjust our current PWM setting
    16.         ramp++;                  // inc our ramp check
    17.         if (ramp >= ADC_ramp)    // check to see if ramp reached rate setting
    18.                     // question: why do I use >= and not == here?
    19.                     // hint: what if you move the rate pot?
    20.         {
    21.             // we have to make a new setting
    22.             ramp = 0;    // reset for next time
    23.             if (ADC_ramp > PWM_Dw)
    24.                 PWM_Dw++;    // running slow
    25.             else
    26.                 PWM_Dw--;    // running fast
    27.             // you can add min & max checks here
    28.         }
    29.         PWMset = PWM_Dw;    // I forget the exact command to set the duty cycle register
    30.                             // but do that here
    31.     }
    32. }[/FONT]
    33.  
    Note as written the fastest rate of change is when the rate pot is set for zero volts in.
     
  6. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Thanks for the replies and the help!

    @Ernie: You're right, I wasn't very clear, but you are pretty much spot on with what you assumed. There are two analog inputs: one determines duty cycle, one determines how fast that duty cycle is allowed to increase/decrease. When I say "gradually increase the duty cycle of the PWM signal" I'm saying that as the analog voltage applied to the "rate" ADC pin decreases, my duty cycle change will occur almost instantaneously. As the analog voltage applied to "rate" ADC pin increases, the duty cycle changes much slower.

    For example, if I had 3 volts at the "rate" ADC pin, and I turned my duty cycle potentiometer to increase the duty cycle from 0% to 50% really fast, it would slowly sweep from 0% to 50%. To be more specific, this change may take up to 3 seconds.

    I appreciate the pseudo-code. I'm going to play with it to see what I can come up with. On another note, I kept playing around yesterday and added some more to what I had previously.

    for (i=0; i <= duty1; i++){
    int ramp_delay0 = (ramp/250);
    delay_ms(ramp_delay0);
    duty = i;
    duty = duty >> 2;
    ccpr1l = duty;
    }

    duty1 is the reading from the ADC. duty is the value sent to the duty cycle register. This sorta works, but the for loop just restarts every time so the duty cycle increases slowly to its maximum then starts over, so it just constantly goes from 0 to the set value repeatedly. I guess I just need to figure out a way to make it stop at the set duty cycle once it reaches the set value then increase or decrease the duty cycle only when the value changes.

    Again, thanks for being patient with me. This is my first PIC project :)
     
  7. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Another update: It's working better, only one small issue. If I change the duty cycle input potentiometer too fast, it seems like the microcontroller can't keep up. Here's the code I worked out:

    duty1 = ADC_duty(0b00000001);
    ramp = ADC_ramp();
    duty_check_out = ccpr1l;
    duty_check_in = duty1 >> 2;
    if (duty_check_out < duty_check_in){
    int ramp_delay0 = (ramp/100);
    ccpr1l++;
    duty_check_out = ccpr1l;
    delay_ms(ramp_delay0);
    }
    if (duty_check_out > duty_check_in){
    int ramp_delay0 = (ramp/100);
    ccpr1l--;
    duty_check_out = ccpr1l;
    delay_ms(ramp_delay0);
    }

    It essentially compares the output duty cycle with the input, and if the output is less, it increases it one step at a time with a delay in between each step until they are equal. It does the same thing if the output duty cycle is too high, except it decrements the output.

    Like I said, this works if I adjust the duty cycle slow enough, but then that's kind of defeating the purpose. I'm going to do some debugging, but if anyone has some suggestions I'm all ears.
     
  8. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Not that anyone is particularly interested, but I solved my problem. I had an extra line of code previously that was resetting a stored variable and keeping the duty cycle from increasing if the potentiometer was turned past the max limit before it ramped up to it. It's hard to explain :p

    Thanks to Ernie and Crowe for helping out. The section of code that ended up solving my problem is shown below. Maybe someone can learn something from this :)

    if (duty_check_out < duty_check_in && duty_check_in <= maxduty){
    int ramp_delay0 = (ramp/100);
    ccpr1l++;
    duty_check_out = ccpr1l;
    delay_ms(ramp_delay0);
    if (duty_check_out >= maxduty)
    ccpr1l = maxduty >> 2;
    }
    if (duty_check_out > duty_check_in && duty_check_in <= maxduty){
    int ramp_delay0 = (ramp/100);
    ccpr1l--;
    duty_check_out = ccpr1l;
    delay_ms(ramp_delay0);
    if (duty_check_out >= maxduty)
    ccpr1l = maxduty >> 2;
    }
     
  9. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,395
    1,607
    psst...jwilk13... two words:

    CODE TAGS!

    Thank you, and good job getting it to work.
     
  10. jwilk13

    Thread Starter Member

    Jun 15, 2011
    228
    12
    Yeah...I was wondering how that works but felt like it was a stupid question :p. So now that the cat's out of the bag, how do those work?
     
  11. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,395
    1,607
    Actually they work rather well. ;)

    When you are in Advanced reply you get a few more choices to click (like those way too cute emoticon icons). Just above your textbox you will see a number sign. Highlight your code and click that icon and it gets wrapped in the proper tags, like so:

    Code ( (Unknown Language)):
    1. // sample code
    2. for (i=0;i<255;i++)
    3. {
    4.     // do something here
    5. }
    Bertus even did a sticky about these 3rd from the top in this forum.

    On some occasions my font defaulted to Verdana messing up my whitespace. If that happens you can always go back and change the tagged text to Courier New, but it should default to Courier New (as mine did this time).
     
Loading...