Creating PWM Using Microcontrollers

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
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}\)

Where:

D = Duty Cycle

t = ON Duration

T = Period of Frequency

\(V_O = D * V_{IN}\)

Where:

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:

Rich (BB code):
//
//    Comet Tails.C
//
    #include <p18f1320.h>
    #include <delays.h>
    #include <stdlib.h>
    #pragma config OSC=INTIO2, WDT=OFF, LVP=OFF, DEBUG=ON
    #define PAUSE Delay1KTCYx(10);
    #define FREQ 500
    #define TIME 100

   void showPat(unsigned int myPat)
    {
        unsigned int patstopBits;
        patstopBits = myPat>>4;
        LATA = myPat;
        LATB = patstopBits;
    }
    
    void showPatN(unsigned int patN)
    {
        unsigned int patstopN;
        patstopN = patN>>4;
        LATA = ~patN;
        LATB = ~patstopN;
    }

    void pwm(unsigned int pins, unsigned int duty, unsigned int freq, unsigned int timer)   // PWM Function
    {    
            unsigned int times, on, off, pat;
            TRISA = ~(pins & 0xFF);
            TRISB = ~(pins>>4 & 0xFF);
            pat = pins;
            on = (65536 - duty);
            off = (65536 - freq) - on;
            INTCONbits.TMR0IF = 0;   // Clear OVF Flag
            showPat(pat);   // Initialize LATA                  
            T0CON|=0x80;    // Set Timer0 Bits
        
            for(times=timer;times>0;times--)   // Duration of playing
            {
                if(freq<=duty)    // If duty cycle is greater than or equal to the frequency
                {
                    showPat(pat);
                }
                else
                {
                     TMR0H = (on >> 8) & 0xFF;   //Load TMR0H byte first
                    TMR0L = on & 0xFF;   // Load TMR0L byte next
                    while(!INTCONbits.TMR0IF);   // Wait for timer
                    INTCONbits.TMR0IF = 0;   // Clear OVF Flag
                    showPatN(pat);   // Invert output
                    TMR0H = (off >> 8) & 0xFF;
                    TMR0L = off & 0xFF;
                    while(!INTCONbits.TMR0IF);
                    showPat(pat);
                }  
            }
    }

    void main()
    {
        int a,b,c,d,e,f;
        OSCCONbits.IRCF0=1; 
        OSCCONbits.IRCF1=1;
        OSCCONbits.IRCF2=1;
        T0CONbits.TMR0ON = 0;   // Don't turn timer on yet
        T0CONbits.T08BIT = 0;     // Timer0 is configured as 16-bit timer
        T0CONbits.T0CS = 0;      // Use internal clock
        T0CONbits.PSA = 1;   // Prescaler is not assigned
        T0CONbits.T0PS2 = 0;
        T0CONbits.T0PS1 = 0;
        T0CONbits.T0PS0 = 0;
        OSCCONbits.IRCF0=1; 
        OSCCONbits.IRCF1=1;
        OSCCONbits.IRCF2=1;
        while(!OSCCONbits.IOFS);

        while(1)
        {
            a = 0x01;
            b = 0x02;
            c = 0x04;
            d = 0x08;
            f = 0x10;
            while(a<500)
            {
                pwm(a, 1, FREQ, TIME);
                PAUSE;
                a=a<<1;
                pwm(b, 25, FREQ, TIME);
                PAUSE;
                b=b<<1;
                pwm(c, 50, FREQ, TIME);
                PAUSE;
                c=c<<1;
                pwm(d, 150, FREQ, TIME);
                PAUSE;
                d=d<<1;
                pwm(e, 350, FREQ, TIME);
                PAUSE;
                e=e<<1;
                pwm(f, 500, FREQ, TIME);
                PAUSE;
                f=f<<1;
            }
        }
    }
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.

Enjoy!
 
Last edited by a moderator:

thatoneguy

Joined Feb 19, 2009
6,359
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.
 

MMcLaren

Joined Feb 14, 2010
861
ELECTRONERD,

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
 

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
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:

Rich (BB code):
pwm(0x01, 1, 1, 1);
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?
 

Attachments

Last edited:

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
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:

Rich (BB code):
//
//    SimplePWMExample.C
//
    #include <p18f1320.h>
    #include <delays.h>
    #include <stdlib.h>
    #pragma config OSC=INTIO2, WDT=OFF, LVP=OFF, DEBUG=ON

    void pwm(char pin, unsigned int duty, unsigned int freq)
    {
        unsigned int off;
        off = 4*(freq - duty);    // OFF Duration = (Freq - Duty Cycle).

        if(off==0)
        {
            LATA = pin;    // If duty cycle and frequency are equal, output is on all the time.
        }
        else
        {
            LATA = pin;    // Turn on pins for ON duration (duty cycle).
            Delay100TCYx(duty); // ON Delay

            LATA = 0x00;    // Turn off pins for OFF duration (Frequency - Duty cycle).
            Delay100TCYx(off); // OFF Delay
        }
    }
            
    void main()
    {
        unsigned char pat = 0x80;    // Variable for TRISA
        unsigned char patstopBits;    // Variable for TRISB
        OSCCONbits.IRCF0=1; 
        OSCCONbits.IRCF1=1;
        OSCCONbits.IRCF2=1;
        while(!OSCCONbits.IOFS);
        TRISA = 0xF0;
        
        pwm(0x01, 1, 10000);
    }
Consider Delay100TCYx(Delay). The frequency is going to be 100 * 0.5uS * Delay.

Let me know if you have any questions!
 
Last edited:

thatoneguy

Joined Feb 19, 2009
6,359
@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?
Essentially, yep. So it is "plug and play" for new users, I'm thinking.
 

MMcLaren

Joined Feb 14, 2010
861
@MMcLaren: 'T' is the period of the frequency you are driving your hardware at.
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;

... which uses the following equation to find frequency:

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".

1MHz Example:

Code:
pwm(0x01, 1, 1, 1);
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.


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:

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
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".
My mistake on the math, simply do 1/f(uS) to get your answer in terms of frequency.

I'm not sure I would claim PWM performance up to 1-MHz if it's just a 50% duty cycle square wave.
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?

And if you can't sustain it indefinitely or run it in the background, how useful would that be?
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.

It seems you're promoting a software PWM "method" that controls one pin at a time without really characterizing performance.
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 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.
It can be useful for more than just LEDs, see the practical examples in my last post.
 

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
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!
 

thatoneguy

Joined Feb 19, 2009
6,359

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.

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

ELECTRONERD

Joined May 26, 2009
1,147
thatoneguy,

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?
 

MMcLaren

Joined Feb 14, 2010
861
Originally Posted by MMcLaren
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".
My mistake on the math, simply do 1/f(uS) to get your answer in terms of frequency.
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 not sure I would claim PWM performance up to 1-MHz if it's just a 50% duty cycle square wave.
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?
I'm just questioning your claims. Here's an excerpt from your first post;
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.
Why would you claim PWM performance up to 1-MHz?

And if you can't sustain it indefinitely or run it in the background, how useful would that be?
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.
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.

It seems you're promoting a software PWM "method" that controls one pin at a time without really characterizing performance.
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.
The code you presented only performs one PWM function at a time for some integer number of loops, yes, no?

It can be useful for more than just LEDs, see the practical examples in my last post.
May I highlight one of those examples, please?
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.
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:

Thread Starter

ELECTRONERD

Joined May 26, 2009
1,147
May I correct an additional imprecision: you cannot use a generic pic (Timer0 is an 8 bits counter) since your software will work only with special mcu with timer0 settable 8/16 bits and it is very difficult that a beginer will use such a device. If he has to buy a new micro than he could choose one with the pwm module on board, don't you think so?


Cheers

Alberto
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

ELECTRONERD

Joined May 26, 2009
1,147
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 mentioned my mathematical error, now it is corrected in the first post.

Why would you claim PWM performance up to 1-MHz?
I'm only indicating the results of math, perhaps it isn't infallible?

\(f = \frac{1}{[(65536 - 65535) * 0.5uS] * 2} = 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.
I'm glad you like the code. ;)

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

The code you presented only performs one PWM function at a time for some integer number of loops, yes, no?
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.

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 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.
 

jpanhalt

Joined Jan 18, 2008
11,087
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.
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 (http://www.parallax.com/Store/Acces...efault.aspx?SortField=ProductName,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.

John

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:

jpanhalt

Joined Jan 18, 2008
11,087
I contrived a PWM function that can drive motors, LEDs, and other hardware from 15.26 Hz to 1 MHz.
So they won't be able to work with servo motors, but mostly with LEDs.
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.

John
 
Top