If you look up PWM on the Wikipedia page, there are some explanations about what it is an its origins. Instead of forcing you read through this heap of information, I’ll give you the condensed version. Simply put, PWM is a way to use a one bit digital output to product a multiple bit digital output. The reason you can do this is because you are adding a time component to the output.
There are many different flavors of PWM. These include center fixed, leading edge fixed, trailing edge fixed and more. For all intents and purposes, they all work on the same principle, so if you are able to understand one, you should be able to figure out the other. In this tutorial, I will show you how to build a circuit and create a low speed DAC from a single PWM output on a PIC24F.
A Closer Look
Essentially, PWM is a way to encode digital information in the time domain using a single output. Imagine a fixed frequency clock signal with a variable duty cycle. These variations in duty cycle can be used to encode digital information. The following examples will illustrate this point
The first waveform is just a 50% duty cycle clock. The signal is posted as a reference to a 1 MHz frequency. The second wavefrom show a two state PWM. Since digital signals can have an off and on state (high and low), the simplest PWM is also just on and off. When a 0 is required, the PWM signal is constantly off (0% duty cycle), when a 1 is required, the PWM signal is constantly on (100% duty cycle).
The third waveform shows a three state PWM. This requires 3 different possible duty cycles. We can pick 0%, 50% and 100% to represent these 3 states.
Following the same principle, the fourth waveform is a PWM with 10 different states. We are dividing a 100% duty cycle into 10 different pieces and each one of those time differential can be used to encode a different PWM output.
When connecting to a device that accepts a PWM signal, there might be additional requirements. The most obvious of these is the frequency. Hobby level servo motors for example, typically accepts signals with a 40 ms period, depending on the model. In addition, the servos might require a minimal and maximal duty cycle. This means that the PWM output with the lowest duty cycle cannot be 0%, and the PWM output with the highest duty cycle cannot be 100%. For typical hobby servo motors, the minimal pulse is about 1 or 2 ms, and the maximum varies depending on the model (typically 10 ms or so). For an application such as LED dimming, there are no restrictions on minimal and maximal duty cycles.
That’s the gist of it. Now let’s get programming.
PIC24F PWM Module
The PIC24F is equipped with “output compare” modules. These modules can be set up in PWM mode and used to produce a PWM output. Setting up the module is extremely easy. However, in order to use the PWM module, one timer will be used. Below is the functional diagram.

As you can see, the PWM module takes advantage of the timers, and compares a PWM countdown value against the PWM period value. When a PWM module is reset, the PWM output is set to high or low depending on how the module is set up. When the countdown value is reached, the PWM output signal is inverted to create the duty cycle. This means that the PWM period value determines the frequency of the PWM, and the countdown value divided by the timer period count value determines the PWM duty cycle. If you need a refresher on how to set up timers, you can read about it here.
The registers in the PWM provides the basic functions to control the PWM such as turning the modules on and off, setting up the leading edge/trailing edge settings and other advanced options. You must also associate the output compare module with a timer. In addition the output pins must be set up through the peripheral pin output table. Before you will see an output however, the timer associated with the PWM module must be started and allowed to run. The period of the PWM is set by the OCxRS (which must be smaller than the period for the associated timer, set using register PRx). Changing the duty cycle of the PWM output is now simply a matter or writing to the register OCxR. The countdown value written to this register must not be smaller than the timer period. If this condition is not satisfied, the behavior of the module is not guaranteed, although I think the module just outputs a 100% duty cycle signal (a signal that is always high).
PWM DAC
If you are using one of the fancier microcontrollers for projects, they might come with a digital to analog converter. These hardware based DACs can be extremely useful in mixed signal applications. With PICs however, this is not the case. As of the writing of this article, I do not know of a PIC24F that comes with a digital to analog converter. However, if the application requires a very slow, low performance DAC, a PWM signal output can be converted into a DAC using the passive properties of resistors and capacitors.
The PWM signal is passed through a simple low pass RC filter. The filter will cause the PWM output to be smoothed out, and if you picked your RC values correctly, the out of the filter will begin to look like analog signal. The following waveform illustrates the point (there is a slight time domain offset between the yellow and green waveforms for some reason).
Code example
In order to illustrate a PWM in action, I have decided to create a sine wave using a PWM output from a PIC24F device. The following images is the circuit used.
Overall Subcircuit Connections and Analog Filter
Microcontroller Circuit
Voltage Regulator
Oscillator
The output of the PWM is on the RP7. This signal is then put through the RC filter R2 and C4. The values are chosen so that the RC time constant is extremely large (I think about 12 seconds or something). For this example, I wanted a sine wave with a frequency of 0.5 to 1 Hz or so (in orders of magnitude, I don’t remember exactly what the frequency was), so I chose the RC time constant to be 5 to 10 times the period. Your application may vary. The code below shows how the sine wave is formed.
“main.c”
/*
NVIS Inc.
Jianyi Liu
Created Sep 29, 2009
Modified Sep 29, 2009
sxoled dumb controller
*/
//define clock speed
#define Fcy 5000000
#define OC1CON_init 0b0000000000000110
#define configoc1_init 0b1011
#include "p24hxxxx .h"
#include "generic .h"
#include "../lib/NvisTypeDef.h"
#include "buttons .h"
#include "../lib/delay.h"
#include "timer1 .h"
#include "timer2 .h"
#include "outcompare .h"
//configuration bits
//no flash, no write protect
_FBS(BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
//no protection
_FGS(GSS_OFF & GCP_OFF & GWRP_OFF);
//external oscillator, single oscillator start
_FOSCSEL(FNOSC_PRI & IESO_OFF);
//monitor, switching off, allow peripheral reconfig
//osc2 as osco, use external clock
_FOSC(FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
//watch dog off
_FWDT(FWDTEN_OFF & WINDIS_OFF);
//turn of POR, use standard I2C pins
_FPOR(FPWRT_PWR1 & ALTI2C_OFF);
//main loop
int main(void)
{
OSCCON = 0x2200; //select Fast RC, no PLL
CLKDIV = 0x0000; //do not divide
//Set up I/O Port
AD1PCFGL = 0xFFFF; //set to all digital I/O
//configure all PortB as input, except RB7 as reset
TRISB = 0xFFFF;
Timer1Init();
RPOR3bits.RP7R = 0b10010;
OpenOC1(OC1CON_init, 10, 10);
ConfigIntOC1(configoc1_init);
while(1)
{
}
}
In the main loop, there’s nothing significant happening except for initializing the modules. Several things to note here. The Microchip peripheral library has been around for quite a while and their code is getting to a point where it is very good for certain modules. There are still bugs here and there, but they are getting ironed out. I use their library code for the output compare. The source code is included with the compiler and is usually located at: “C:\Program Files (x86)\Microchip\mplabc30\v3.25\src” on my machine. This is the reason for including the “outcompare.h” file. I call the initialize function OpenOC1() which sets up the Output compare module. For more information about this function, consult the header file associated with the module. Next, I setup the timer, and make sure that the peripheral IO pin is set to an output and configured for an output compare. Then is off to the forever while loop construct.
“timer1.c”
/*
NVIS Inc.
Jianyi Liu
Created Sep 29, 2009
Modified Sep 29, 2009
timer main
*/
#include "p24hxxxx.h"
#include "timer1.h"
#include "buttons.h"
#include "outcompare.h"
#include "math.h"
//prototype functions
#define amp1_1 100
#define frq1_1 25
#define frq1_2 25
#define dcb1_1 2
#define dcb1_2 3
#define dcb1_3 4
double const pi = 3.1415926535;
double sinarg1 = 0;
double dacdouble1 = 0;
unsigned int timecount1 = 0;
unsigned int amp1 = amp1_1;
unsigned int frq1 = frq1_1;
unsigned int dcb1 = dcb1_1;
void Timer1Init(void)
{
PR1 = 5000;
IPC0bits.T1IP = 5;
T1CON = 0b1000000000000000;
IFS0bits.T1IF = 0;
IEC0bits.T1IE = 1;
}
unsigned char Timer1IsOverflowEvent(void)
{
if (IFS0bits.T1IF)
{
IFS0bits.T1IF = 0;
return(1);
}
return(0);
}
//Interrupt vector, if needed
void __attribute__((__interrupt__, auto_psv)) _T1Interrupt(void)
{
//calculate DAC output
//get increment current time
if (timecount1 < 2*frq1)
timecount1 ++;
else
timecount1 = 0;
//calculate sin value
dacdouble1 = amp1*sin(timecount1*pi/frq1) + amp1 + dcb1;
//load to dac
SetDCOC1PWM((int)dacdouble1);
//reset interrupt flag
IFS0bits.T1IF = 0;
}
I didn’t include the “timer1.h” header files as they are all just variable declarations and function prototypes. I used the standard math header “math.h” for this example to calculate the amplitude for the sine wave. The interrupt routine is very standard. It calculates the sine values and posts them to the OCxR register by using the function SetDCOC1PWM(int).
I’ve taken the liberty to show you the final output on an oscilloscope. There are several things to notice about PWM DACs. First, the granularity of the DAC conversion depends highly on how fine you can discretely modify your duty cycle. As an example, if your timer period only counts to 16, you cannot have a D-to-A conversion of greater than 4 bits. Conversely however, in the same scenario, the conversion frequency can be set to extremely high since the timer period only has to count to 16. For an internal instruction clock running at 16 MHz, with the timer counting to 16, the DAC can operate at 1 MHz with a conversion resolution of 4 bits. If you want to improve the resolution to 8 bits, then the timer counter will have to be set to count to 256. If the internal instruction clock is still running at 16 MHz, then the DAC can only operate at 1/16 MHz.
The yellow trace is the analog output and the green is the PWM output. And that’s how you use the PWM module.




Thanks for keeping the site going! You have made my life a little easier.
Very informative. Examples are good. Keep it going