Creating PWM Using Microcontrollers

Discussion in 'Embedded Systems and Microcontrollers' started by ELECTRONERD, Oct 26, 2010.


    Thread Starter Senior Member

    May 26, 2009
    Hey Everybody,

    If you want to create PWM using a PIC, then this thread is for you. I contrived a PWM function that can drive motors, LEDs, and other hardware from 15.26 Hz to 1 MHz. I also put in a Comet lighting effect for 10 LEDs (RA0-RA3, RB0-RB5) on the PIC18F1320 just for a quick example program. Please note that the compiler is C18 and I am using the PIC18F1320. The code will not work with a different compiler or PIC. To understand how the code works, you need to grasp the concept of duty cycle and PWM itself. With PWM, you are turning on your hardware for a limited amount of time, and turning it off for a limited amount of time. That time differs with respect to your driving frequency, in which you subtract your frequency from the duty cycle to get the OFF delay. Therefore, the following equations apply:

    D = \frac{t}{T}


    D = Duty Cycle

    t = ON Duration

    T = Period of Frequency

    V_O = D * V_{IN}


    Vo = Output voltage from pin.

    Vin = Standard voltage input, presumably 5V (What you are doing is switching that 5V on and off rapidly so you get a new output voltage).

    So in the code, you see I have my on and off variables, which determine the on and off delay. The reason you see 65536 subtracting from the variables is due to Timer0, which uses the following equation to find frequency:

    f = \frac{1}{(65536 - Delay)}

    Please note that the (65536 - Delay) is in microseconds (μS). Here's a simple example of how it works:

    Suppose hypothetically you wanted a frequency of 1 kHz. You simply put it in the PWM function and it does the rest. It begins in the code by subtracting 65536 from your intended frequency value:

    Code Math:

    (65536 - 1000) = 65436

    f = \frac{1}{[(65536 - 65436) * 0.5uS] * 2} = 1 kHz

    The showPat function simply combines LATA and LATB together, otherwise my LED combination wouldn't be consecutive.

    Here is the code:

    Code ( (Unknown Language)):
    2. //
    3. //    Comet Tails.C
    4. //
    5.     #include <p18f1320.h>
    6.     #include <delays.h>
    7.     #include <stdlib.h>
    8.     #pragma config OSC=INTIO2, WDT=OFF, LVP=OFF, DEBUG=ON
    9.     #define PAUSE Delay1KTCYx(10);
    10.     #define FREQ 500
    11.     #define TIME 100
    13.    void showPat(unsigned int myPat)
    14.     {
    15.         unsigned int patstopBits;
    16.         patstopBits = myPat>>4;
    17.         LATA = myPat;
    18.         LATB = patstopBits;
    19.     }
    21.     void showPatN(unsigned int patN)
    22.     {
    23.         unsigned int patstopN;
    24.         patstopN = patN>>4;
    25.         LATA = ~patN;
    26.         LATB = ~patstopN;
    27.     }
    29.     void pwm(unsigned int pins, unsigned int duty, unsigned int freq, unsigned int timer)   // PWM Function
    30.     {    
    31.             unsigned int times, on, off, pat;
    32.             TRISA = ~(pins & 0xFF);
    33.             TRISB = ~(pins>>4 & 0xFF);
    34.             pat = pins;
    35.             on = (65536 - duty);
    36.             off = (65536 - freq) - on;
    37.             INTCONbits.TMR0IF = 0;   // Clear OVF Flag
    38.             showPat(pat);   // Initialize LATA                  
    39.             T0CON|=0x80;    // Set Timer0 Bits
    41.             for(times=timer;times>0;times--)   // Duration of playing
    42.             {
    43.                 if(freq<=duty)    // If duty cycle is greater than or equal to the frequency
    44.                 {
    45.                     showPat(pat);
    46.                 }
    47.                 else
    48.                 {
    49.                      TMR0H = (on >> 8) & 0xFF;   //Load TMR0H byte first
    50.                     TMR0L = on & 0xFF;   // Load TMR0L byte next
    51.                     while(!INTCONbits.TMR0IF);   // Wait for timer
    52.                     INTCONbits.TMR0IF = 0;   // Clear OVF Flag
    53.                     showPatN(pat);   // Invert output
    54.                     TMR0H = (off >> 8) & 0xFF;
    55.                     TMR0L = off & 0xFF;
    56.                     while(!INTCONbits.TMR0IF);
    57.                     showPat(pat);
    58.                 }  
    59.             }
    60.     }
    62.     void main()
    63.     {
    64.         int a,b,c,d,e,f;
    65.         OSCCONbits.IRCF0=1;
    66.         OSCCONbits.IRCF1=1;
    67.         OSCCONbits.IRCF2=1;
    68.         T0CONbits.TMR0ON = 0;   // Don't turn timer on yet
    69.         T0CONbits.T08BIT = 0;     // Timer0 is configured as 16-bit timer
    70.         T0CONbits.T0CS = 0;      // Use internal clock
    71.         T0CONbits.PSA = 1;   // Prescaler is not assigned
    72.         T0CONbits.T0PS2 = 0;
    73.         T0CONbits.T0PS1 = 0;
    74.         T0CONbits.T0PS0 = 0;
    75.         OSCCONbits.IRCF0=1;
    76.         OSCCONbits.IRCF1=1;
    77.         OSCCONbits.IRCF2=1;
    78.         while(!OSCCONbits.IOFS);
    80.         while(1)
    81.         {
    82.             a = 0x01;
    83.             b = 0x02;
    84.             c = 0x04;
    85.             d = 0x08;
    86.             f = 0x10;
    87.             while(a<500)
    88.             {
    89.                 pwm(a, 1, FREQ, TIME);
    90.                 PAUSE;
    91.                 a=a<<1;
    92.                 pwm(b, 25, FREQ, TIME);
    93.                 PAUSE;
    94.                 b=b<<1;
    95.                 pwm(c, 50, FREQ, TIME);
    96.                 PAUSE;
    97.                 c=c<<1;
    98.                 pwm(d, 150, FREQ, TIME);
    99.                 PAUSE;
    100.                 d=d<<1;
    101.                 pwm(e, 350, FREQ, TIME);
    102.                 PAUSE;
    103.                 e=e<<1;
    104.                 pwm(f, 500, FREQ, TIME);
    105.                 PAUSE;
    106.                 f=f<<1;
    107.             }
    108.         }
    109.     }
    Informative Links:

    Duty cycle - Wikipedia, the free encyclopedia

    Pulse-width modulation - Wikipedia, the free encyclopedia

    If anyone has questions, I would encourage you to ask them! Comments and suggestions are also welcomed.

    Last edited by a moderator: Nov 15, 2010
    douxiang and thatoneguy like this.
  2. retched

    AAC Fanatic!

    Dec 5, 2009
    Do you have a schematic to show readers how the PIC is connected to the actual circuit?

    I think a schematic and a photo of the breadboarded circuit would polish this up a bit.
    douxiang likes this.
  3. thatoneguy

    AAC Fanatic!

    Feb 19, 2009
    Thanks for this!

    To make it more of an "importable library", modify the code to save the registers and latches, then restore them on exit, or make the output port user defined when the library is called?

    I'm thinking this could be a neat add on for already completed projects that could be "spiffed up" with better motor control, or dimmer LEDs, etc. It is a common request here, as you know.
  4. MMcLaren

    Well-Known Member

    Feb 14, 2010

    I'm just wondering if you're confusing "frequency" with "period"?

    Can you show an example of PWM with a 1-MHz frequency (a 1-usec period)? How much duty cycle resolution do you have in that 1-MHz PWM example?

    Thank you.

    Kind regards, Mike
  5. beenthere

    Retired Moderator

    Apr 20, 2004
    Per a suggestion, we will make this sticky.

    Thread Starter Senior Member

    May 26, 2009
    Here's the schematic I used in the example LED program, as retched requested.

    @MMcLaren: 'T' is the period of the frequency you are driving your hardware at. So if you want to drive a servo motor at 50 Hz, you would simply do the following:

    P = \frac{1}{f}

    P = \frac{1}{50} = 20mS

    So ideally you are converting your driving frequency in terms of a period value. I think the resolution depends on your driving frequency. I'm not sure of the accuracy, but I will look into it and let you know.

    1MHz Example:

    Code ( (Unknown Language)):
    1. pwm([COLOR=Red]0x01[/COLOR], [COLOR=Green]1[/COLOR], [COLOR=Blue]1[/COLOR], [COLOR=Magenta]1[/COLOR]);
    0x01 = Pin
    1 = Duty Cycle
    1 = Frequency [(65536 - 65535)*0.5uS]*2 = 1 MHz. (Remember I first subtract 65536 in the code, so that's how I get 65535 in the equation)
    1 = Entered in for loop, will account for how long you drive PWM function.

    @thatoneguy: I'm assuming that you are referring to the TRIS register? I see you noticed that I have it in the PWM function, but they can be transferred to the main function. Is that what your asking?

    You can do this with LEDs, motors, speakers, and other hardware as well.

    Practical Examples of Using PWM Function:

    • Set the driving frequency to a speaker and adjust the volume by adjusting the duty cycle. This could put more feeling into music you could generate.
    • Connect as many LEDs as you can to your PIC or use a shift register to add more while dimming them by varying the duty cycle. Make sure you increase the frequency to a reasonable amount so you can't notice any flicker.
    • Drive a servo motor or typical motor with the PWM function. With servo motors, they are centered at 1.5mS (≈666.67 Hz). Going below or above this frequency will change the servo motors rotation to Clockwise or Counter-Clockwise. You can also slow down the servo motors by decreasing the duty cycle.

    I'll work on adding more comments to the code, as to make it more understandable. I can also move the TRIS commands to the main function, would you prefer that?
    Last edited: Oct 28, 2010

    Thread Starter Senior Member

    May 26, 2009
    This code uses delay commands so it might be easier for people to understand. In the former code, I used the 16-Bit Timer0, which can provide a broader range of frequency and is more accurate. This code is the simple analogy involved with PWM:

    Code ( (Unknown Language)):
    1. //
    2. //    SimplePWMExample.C
    3. //
    4.     #include <p18f1320.h>
    5.     #include <delays.h>
    6.     #include <stdlib.h>
    7.     #pragma config OSC=INTIO2, WDT=OFF, LVP=OFF, DEBUG=ON
    9.     void pwm(char pin, unsigned int duty, unsigned int freq)
    10.     {
    11.         unsigned int off;
    12.         off = 4*(freq - duty);    // OFF Duration = (Freq - Duty Cycle).
    14.         if(off==0)
    15.         {
    16.             LATA = pin;    // If duty cycle and frequency are equal, output is on all the time.
    17.         }
    18.         else
    19.         {
    20.             LATA = pin;    // Turn on pins for ON duration (duty cycle).
    21.             Delay100TCYx(duty); // ON Delay
    23.             LATA = 0x00;    // Turn off pins for OFF duration (Frequency - Duty cycle).
    24.             Delay100TCYx(off); // OFF Delay
    25.         }
    26.     }
    28.     void main()
    29.     {
    30.         unsigned char pat = 0x80;    // Variable for TRISA
    31.         unsigned char patstopBits;    // Variable for TRISB
    32.         OSCCONbits.IRCF0=1;
    33.         OSCCONbits.IRCF1=1;
    34.         OSCCONbits.IRCF2=1;
    35.         while(!OSCCONbits.IOFS);
    36.         TRISA = 0xF0;
    38.         pwm(0x01, 1, 10000);
    39.     }
    Consider Delay100TCYx(Delay). The frequency is going to be 100 * 0.5uS * Delay.

    Let me know if you have any questions!
    Last edited: Oct 28, 2010
    Alberto likes this.
  8. thatoneguy

    AAC Fanatic!

    Feb 19, 2009
    Essentially, yep. So it is "plug and play" for new users, I'm thinking.
  9. MMcLaren

    Well-Known Member

    Feb 14, 2010
    I know the difference between PWM "frequency" and "period" I was just curious if you knew based on the following quote from your original post;

    That formula seems to be for "period" as does the formula in the quote below. Should be "period = ((65536-65535)*0.5us*2 = 1 usec" and "frequency = 1 / 1 usec = 1 MHz".

    I'm not sure I would claim PWM performance up to 1-MHz if it's just a 50% duty cycle square wave. And if you can't sustain it indefinitely or run it in the background, how useful would that be?

    It seems you're promoting a software PWM "method" that controls one pin at a time without really characterizing performance. It might be useful for PWM brightness control for a handful of LEDs. Not sure how useful it might be for driving a motor if you're going to interrupt the PWM output every time the loop count runs out.

    I can't use it for anything but thank you anyway for sharing. I'm sure it will be useful for some people.

    Cheerful regards, Mike
    Last edited: Oct 28, 2010

    Thread Starter Senior Member

    May 26, 2009
    My mistake on the math, simply do 1/f(uS) to get your answer in terms of frequency.

    If you want to get real eclectic, I suppose I can examine the output on an oscilloscope? How often are you going to have someone drive hardware at 1 MHz anyway?

    Who said you can't sustain it indefinitely or run it in the background? Just put a while(1) loop in there if you want it to run presumably forever. If you have had any code experience, this is a simple fix.

    Though it may seem that way, that's not the case. Note that if you put a '1' for the timer loop, you will be switching rapidly between all the pins you put in the function.

    It can be useful for more than just LEDs, see the practical examples in my last post.

    Thread Starter Senior Member

    May 26, 2009
    I don't mean to sound unfriendly Mike, I apologize if it may have seemed that way. I'm sure you have had more experience with firmware than I have, so I appreciate all your input on this.

    Feel free to provide any further useful tips or comments!
  12. thatoneguy

    AAC Fanatic!

    Feb 19, 2009
    There are alot of people new to PIC programming that want to simply dim an LED, or do some other PWM effect. Not all PICs have the hardware PWM Module. This code is not only an excellent template to get many people started in PIC programming, but also gives a PWM library for same. Once you've read the same questions for a year, it is refreshing to have a place to point for a "solution" that covers the majority of PIC questions.

    Once comfortable with the software routine above, adding it to other code or expanding on it can be made fairly simple (my suggestions above, maybe use #define for the port, etc., is all)

    The only other addition I'd suggest is a hotlink to the compiler used in the OP, and maybe one to the PicKit 2 44 pin dev board combo for $49-ish, so it is a "One Stop Info Shop" in a tacked thread.

    Thread Starter Senior Member

    May 26, 2009

    I am also working on a tutorial that compares the LAT and PORT registers, if you are interested? It's been observed that both registers seem to be interchangeable, but that isn't true at all and the tutorial underlines the primary differences. I just have to revise it and I'll post it in a new thread.

    Eventually I might consider making a a "Quick-Start Guide" tutorial for PIC MCU's, which will include the main requirements in for beginning code (OSC commands, ADCON commands, etc) and then how to wire the hardware on a breadboard.

    Don't expect the latter tutorial anytime soon, I don't have a lot of time on my hands. Nevertheless, I'll try to get it done as soon as I can.

    I'll edit the code to accommodate your recommendations, give me some time. What all exactly do you think I should improve or add?
  14. MMcLaren

    Well-Known Member

    Feb 14, 2010
    You're still not making sense. Frequency (in Hertz) is the reciprocol of the Period. Surely you're not using the symbol 'f' for period, are you?

    I'm just questioning your claims. Here's an excerpt from your first post;
    Why would you claim PWM performance up to 1-MHz?

    You're the one presenting this wonderful and supposedly "do all" code. If it needs "fixes" to be useful then perhaps this material is better suited to a blog.

    The code you presented only performs one PWM function at a time for some integer number of loops, yes, no?

    May I highlight one of those examples, please?
    Maybe we're not talking about the same type of servo motor? On the ones I'm familiar with you change the position by varying the pulse width (duty cycle), not the frequency. Servos require a pulse of between 1.0 and 2.0 msecs with 1.5 msecs as the 'center' position. The pulse is repeated at 20-msec intervals or a 20-msec PERIOD (frequency 50-Hz). The 1.0 to 2.0 msec pulse represents a duty cycle range of 5 to 10%. I have no idea what you're trying to describe.

    I'm glad you're having fun and I understand wanting to share but you really need to tone down your claims and get a better handle on the concepts, terminology, and language.

    Good luck on your projects. Have fun.

    Cheerful regards, Mike
    Last edited: Oct 28, 2010

    Thread Starter Senior Member

    May 26, 2009
    Yes, only in some instances may Timer0 be configured as a 16-Bit timer for various PICs. They can use 8-Bits, but they won't be able to go as low as ≈15 Hz. With an 8-Bit timer, here's the max and min values:

    Min = 3906.25 Hz

    Max = 1 MHz (Still disputed)

    So they won't be able to work with servo motors, but mostly with LEDs. That being the case, I would recommend that they get a easy to use PIC with a 16-Bit timer, as you implied. The PIC18F1320 is one such MCU, and is very easy to get started with for most beginner applications.

    Thread Starter Senior Member

    May 26, 2009
    I mentioned my mathematical error, now it is corrected in the first post.

    I'm only indicating the results of math, perhaps it isn't infallible?

    f = \frac{1}{[(65536 - 65535) * 0.5uS] * 2} = 1 MHz

    I'm glad you like the code. ;)

    I've tested the code and it works, what more do you want me to do?

    Nope. You can do 0x03, 0xFF, or how many I/O pins you have. I admit that might not have been explained clearly before, sorry.

    I guess we aren't talking about the same servo. I've had experience with parallax's servo motor, and apparently altering frequency will adjust clockwise/counter-clockwise rotation while varying the duty cycle will effectively vary the speed of rotation. This makes sense to me in theory.
  17. jpanhalt

    AAC Fanatic!

    Jan 18, 2008
    Parallax "makes" different servos. Are you talking about a continuous rotation servo or a positioning servo? If you look here, you will see that their continuous rotation servo is just a modified Hitec servo. The Parallax servo controller (,ProductName) appears to use conventional control signals, i.e., duty cycle controls position. It uses ramping to the target duty cycle to control speed.

    I have no experience with servos modified for continuous rotation, but apparently based on your experience, the interaction of frequency and duty cycle is quite different from unmodified servos. (Why?) It is important that you make a clear distinction of the type of servo you are using and whether your program will control an unmodified servo. BTW, in a unmodified Hitec servo, frequency to a very limited extent can control speed. The Parallax servo controller (above) uses ramping as a preferred method to control speed.


    Edit: Just noticed I got caught up in a sloppy use of the term "duty cycle." A typical controller for model servos, such as this, does not control position as a function of duty cycle of the incoming signal (within limits). Position is controlled by the absolute width of the control signal in mS, which is relatively independent of the frequency, i.e., % duty cycle.
    Last edited: Oct 29, 2010
  18. jpanhalt

    AAC Fanatic!

    Jan 18, 2008
    This program is basically an LED dimmer. It is certainly not for controlling motors or servos, at least not as explained so far by Electronerd.

    Perhaps in light of the limitations that have become evident for this program, the thread should be moved to the Completed Projects forum. A "sticky" implies endorsement by AAC of a level of validity and usefulness that apparently this program lacks.

  19. lloydi12345


    Aug 15, 2010
    Hello, do you have an PIC asm code for this one?

    Thread Starter Senior Member

    May 26, 2009
    I don't know assembly, as I have had no experience in that regard. Perhaps you can make one using the algorithms I provided?