Slow Duty Cycle Increase w/ PIC

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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.
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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.
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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.
 

MCrowe

Joined May 29, 2011
69
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?
 

ErnieM

Joined Apr 24, 2011
8,377
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:

Rich (BB code):
unsigned int ramp = 0;  // used to calculate our rate of change
unsigned int ADC_Dw;    // raw A2D reading for final Dw
unsigned int ADC_ramp;  // raw A2D reading rate of change
unsigned int PWM_Dw;    // current calculation of Dw

while(1) 
{
    delayMs(SomeNiceValue); // this delay sets our max rate of change (resolution) 
    ReadA2D(ADC_Dw);        // get fresh Dw
    ReadA2D(ADC_ramp);      // get fresh ramp final setting
    if (ADC_ramp != PWM_Dw)
    {
        // our current pot setting does not match what the PWM is set for
        // so we have to adjust our current PWM setting
        ramp++;                  // inc our ramp check
        if (ramp >= ADC_ramp)    // check to see if ramp reached rate setting
                    // question: why do I use >= and not == here?
                    // hint: what if you move the rate pot?
        {
            // we have to make a new setting
            ramp = 0;    // reset for next time
            if (ADC_ramp > PWM_Dw)
                PWM_Dw++;    // running slow
            else
                PWM_Dw--;    // running fast
            // you can add min & max checks here
        }
        PWMset = PWM_Dw;    // I forget the exact command to set the duty cycle register
                            // but do that here
    }
}
Note as written the fastest rate of change is when the rate pot is set for zero volts in.
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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 :)
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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.
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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;
}
 

Thread Starter

jwilk13

Joined Jun 15, 2011
228
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?
 

ErnieM

Joined Apr 24, 2011
8,377
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?
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:

Rich (BB code):
// sample code
for (i=0;i<255;i++)
{
    // do something here
}
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).
 
Top