Pic PWM programming in C, cant figure out how to change the duty cycle

Thread Starter

Travm

Joined Aug 16, 2016
356
I feel really stupid not being able to make this work.
I am using XC8 compiler, inside MPLAB.
I am using the Microchip Code Configurator.

My problem is, that i can get the PWM to start, and i can change its initial value by modifying the registers in the initialization file pwm.c
Code:
//PWM1DCH 0;
    PWM1DCH = 0x00;

     //PWM1DCL 200;
    PWM1DCL = 200;

     //PWM1PRH 0;
    PWM1PRH = 0x00;

     //PWM1PRL 255;
    PWM1PRL = 0xFF;
But, when I put the following into my while loop for the main program, I cant seem to control the duty cycle. Clearly i'm doing something fundamentally wrong, but i'm too new to this to see it. This is towards the bottom of my main.c file. I didnt modify anything beyond adding the PWM1DCL = 1;
Code:
while (1)
    {
    
       PWM1DCL = 1;
      
        // Add your application code
    
    
    }
I expected this to turn on, and immediately adjust the pwm to a very low duty cycle. But it does not, it stays at 200.
 

Papabravo

Joined Feb 24, 2006
17,621
you need to read the datasheet -> you don't know how the module works.
This is always good advice. I tend to want to make sure I know what each and every register of a peripheral does. I also want to know what the default initial values are and I make double sure that every register gets initialized just in case the initialization happens for a reason other than a power on RESET.

BTW -- which processor are you using? Could you help us out with a link to the datasheet.?
 

Thread Starter

Travm

Joined Aug 16, 2016
356
PIC12F157x or PIC16F157x I would guess.

You need to read the "Reload Operation" chapter in the Datasheet.
*THIS.
having read this and not fully understood what i'm reading. This is helpful. Glad to hear this forum has some decent people.

I haven't tested yet. Will test tomorrow. 1000 Thanks.

As pointed out, i didnt specify my chip. Rookie mistake. Its a 16f1574.

and the datasheet link as requested.
http://ww1.microchip.com/downloads/en/DeviceDoc/40001782C.pdf
 

dannyf

Joined Sep 13, 2015
2,197
not fully understood what i'm reading.
any pwm module is fairly simple in that

1) it has a time base generator that determines the frequency;
2) it has a compare generator that determines the duty cycle;
3) it has a pin management / logic block that determines what to do at reset/compare match;

all you need is to figure out what those corresponding parts are for your target.
 

Thread Starter

Travm

Joined Aug 16, 2016
356
Simple is relative sir. This is not my field of expertise.
I did believe I understood how all of those things worked. When it didn't work, I asked for ideas as to what I was overlooking.
I did forget to mention my chip, which secondary makes it hard to help.
 

johndeaton

Joined Sep 23, 2015
63
Hi Travm,

Try this.

Code:
while (1)
{

     PWM1DCL = 1;

     // delay 1 sec (whatever you use for delays)

     PWM1DCL = 50;

     // delay 1 sec (whatever you use for delays)

}
The way PWM works in MCUs is the value in the timer counter is compared to a certain value set by the programmer and the output is toggled when they match. I am thinking you are probably constantly resetting the value with that function call, so it is never getting there. I don't have access to the Microchip library right now, so I'm not 100% sure. Let me know if it works.

John
 

johndeaton

Joined Sep 23, 2015
63
I see the issue now. The reason it isn't working is that register is left-aligned. Try this instead...

Code:
PWM1DCL = 1 << 6;
Or, use the function call that is already part of the Microchip library (it's in pwm1.c)

Code:
 void PWM1_LoadDutyValue(uint16_t dutyValue)
{
     // Writing to 8 MSBs of PWM duty cycle in PWMDCH register
     PWM1DCH = (dutyValue & 0x03FC)>>2;
    
     // Writing to 2 LSBs of PWM duty cycle in PWMDCL register
     PWM1DCL = (dutyValue & 0x0003)<<6;
}
 

Thread Starter

Travm

Joined Aug 16, 2016
356
I see the issue now. The reason it isn't working is that register is left-aligned. Try this instead...

Code:
PWM1DCL = 1 << 6;
Or, use the function call that is already part of the Microchip library (it's in pwm1.c)

Code:
 void PWM1_LoadDutyValue(uint16_t dutyValue)
{
     // Writing to 8 MSBs of PWM duty cycle in PWMDCH register
     PWM1DCH = (dutyValue & 0x03FC)>>2;
   
     // Writing to 2 LSBs of PWM duty cycle in PWMDCL register
     PWM1DCL = (dutyValue & 0x0003)<<6;
}
When I tried to use that function call I received an error on build.
I will double check that I didn't overlook the bit alignment, I don't believe i did because it responds properly when I modify the registers on initialization.
If I understand correctly since i an only trying to get 8 bit resolution I can set pwm1dch to 0 and leave it there.
So much of this is new to me, binary numbers, hexadecimal, c programming, microcontrollers. I'm pushing an extremely steep learning curve for myself here. I really appreciate the help.
 

johndeaton

Joined Sep 23, 2015
63
When I tried to use that function call I received an error on build.
That's strange. That will be the easiest way to get it to work correctly. Is the mcc header defined in the source file?

Code:
#include "mcc_generated_files/mcc.h"
After you have that defined, you should be able to just call the function like this...

Code:
PWM1_LoadDutyValue(200);
The function will shift the bits for you.



Not on this chip.
Then why does the Microchip code left shift the bits?

Code:
void PWM1_LoadDutyValue(uint16_t dutyValue)
{
     // Writing to 8 MSBs of PWM duty cycle in PWMDCH register
     PWM1DCH = (dutyValue & 0x03FC)>>2;
   
     // Writing to 2 LSBs of PWM duty cycle in PWMDCL register
     PWM1DCL = (dutyValue & 0x0003)<<6;
}
 

Thread Starter

Travm

Joined Aug 16, 2016
356
No. You set PWM1DCL to zero if you want that.
hmm, ok. it seems when i use the MCC any number under 255 has PWM1DCH set to 0, only when i get beyond 8 bits does PWM1DCH become a non-zero number. Regardless my issue was entirely not understanding that I had to set the LDA bit every time.
Here is my working code.
First the relevant registers in pwm.c Note PWM1DCH is 0, and PWM1DCL is 255. (If i'm doing this wrong, which is highly probable, please feel free to correct me/point me to whatever I didnt read properly)
Code:
void PWM1_Initialize(void)
{
    // set the PWM1 to the options selected in the User Interface

     //PHIE disabled; DCIE disabled; OFIE disabled; PRIE disabled;
    PWM1INTE = 0x00;

     //PHIF cleared; OFIF cleared; DCIF cleared; PRIF cleared;
    PWM1INTF = 0x00;

     //PS No_Prescalar; CS FOSC;
    PWM1CLKCON = 0x00;

     //LDS LD1_trigger; LDT disabled; LDA do_not_load;
    PWM1LDCON = 0x00;

     //OFM independent_run; OFS OF1_match; OFO match_incrementing;
    PWM1OFCON = 0x00;

     //PWM1PHH 0;
    PWM1PHH = 0x00;

     //PWM1PHL 0;
    PWM1PHL = 0x00;

     //PWM1DCH 0;
    PWM1DCH = 0x00;

     //PWM1DCL 5;
    PWM1DCL = 0x05;

     //PWM1PRH 0;
    PWM1PRH = 0x00;

     //PWM1PRL 255;
    PWM1PRL = 0xFF;

     //PWM1OFH 0;
    PWM1OFH = 0x00;

     //PWM1OFL 1;
    PWM1OFL = 0x01;

     //PWM1TMRH 0;
    PWM1TMRH = 0x00;

     //PWM1TMRL 0;
    PWM1TMRL = 0x00;
  
     //MODE standard_PWM; POL active_hi; EN enabled;
    PWM1CON = 0x80;
}
and my while loop in main.c Stepped it up a notch to demonstrate to myself how the duty cycle in changed. Does exactly what i would expect. Starts dim, then steps it up in brightness in 1s intervals. *edit, i am controlling an LED with this PWM signal btw.
Code:
while (1)
    {
      
       PWM1DCL = 1;
       PWM1LDCON = 0x80;
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       PWM1DCL = 75;
       PWM1LDCON = 0x80;
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       PWM1DCL = 150;
       PWM1LDCON = 0x80;
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       PWM1DCL = 225;
       PWM1LDCON = 0x80;
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
       __delay_ms(250);
      
        
        // Add your application code
      
      
    }
}
 
Last edited:

Thread Starter

Travm

Joined Aug 16, 2016
356
Is there a more intuitive way to set an individual bit than specifying the binary equivalent of an 8-bit register value in hexadecimal?
it seems that being able to code something like
setLDAbit = 1 is more intuitive to me than
PWM1LDCON = 0x80
Hexadecimal is annoying to me. especially when trying to use hexadecimal to represent a binary number.
 

Thread Starter

Travm

Joined Aug 16, 2016
356
That's strange. That will be the easiest way to get it to work correctly. Is the mcc header defined in the source file?

Code:
#include "mcc_generated_files/mcc.h"
After you have that defined, you should be able to just call the function like this...

Code:
PWM1_LoadDutyValue(200);
The function will shift the bits for you.





Then why does the Microchip code left shift the bits?

Code:
void PWM1_LoadDutyValue(uint16_t dutyValue)
{
     // Writing to 8 MSBs of PWM duty cycle in PWMDCH register
     PWM1DCH = (dutyValue & 0x03FC)>>2;
  
     // Writing to 2 LSBs of PWM duty cycle in PWMDCL register
     PWM1DCL = (dutyValue & 0x0003)<<6;
}
Yes the mcc.h is included. here is what that function looks like in my file. It doesn't left shift the bits.
Code:
void PWM1_DutyCycleSet(uint16_t dutyCycleCount)
{
    PWM1DCH = (dutyCycleCount>>8);    //writing 8 MSBs to PWMDCH register
    PWM1DCL = (dutyCycleCount);    //writing 8 LSBs to PWMDCL register       
}
now I understand completely what is going on here. It takes a 16 bit number, and converts it to two 8-bit values and puts them in the proper registers.

I think my problem was that it was expecting a 16 bit number, and when i gave it 200, 200 is an 8-bit number and the compiler choked on it.
I haven't tested this theory.
 

johndeaton

Joined Sep 23, 2015
63
Hi Travm,

Glad to hear you got your code working!

Code:
Yes the mcc.h is included. here is what that function looks like in my file. It doesn't left shift the bits.
Sorry. This was my fault. I had the wrong chip defined in MPLab X when I wrote this. That code goes with a PIC16F1508. Sorry to mislead you.


Code:
Is there a more intuitive way to set an individual bit than specifying the binary equivalent of an 8-bit register value in hexadecimal?


There is probably a better way to do this, but one method is defining bits somewhere in one of your header files like this...

Code:
#define BIT0 0x01
#define BIT1 0x02
#define BIT7 0x80
Then, when you want to set a bit, you can do it like this...

Code:
PWM1LDCON |= BIT7;
And to clear a bit...

Code:
PWM1LDCON &= ~BIT7;
 
Last edited:
Top