SP.710, Spring 2001: "Intro to Microcontrollers"

Oscillators, Buzzers, and Dimmers: The PIC PWM generator

What is PWM? It's "Pulse Width Modulation". It will be explained further today in class.

With that explanation out of the way, here's a quick guide to doing PWM on the PIC. You could do it by hand with delay_us commands, or by using the Timers, but there is a much easier way:

The PIC has a "hardware PWM generator" that can automatically generate a PWM signal at a duty cycle you choose (up to 10 bits of resolution, which is roughly 0.1% increments), and at one of three frequencies (roughly 1.25 kHz, 5 kHz, and 20 kHz, with a 20MHz crystal). There are ways to vary these frequencies more precisely, but we will get to that later.

The C commands to use this PWM generator are:

(commands you would usually only run once, at the beginning of main():)

  SETUP_CCP1(CCP_PWM);         // setup pin CCP1 (RC2) to do PWM

  SETUP_TIMER_2(T2_DIV_BY_16,255,1)  // set the PWM frequency to 
   // 20 MHz / 4 clocks-per-instr / 16 (DIV_BY_16) / 256 (ticks/rollover)
   // = 1.25 kHz (change to T2_DIV_BY_4 for 5 kHz, T2_DIV_BY_1 for 20 kHz)

(and then, to actually use the PWM generator:)

  set_PWM1_duty(#);  // # = 0 (full off) --> 255 (100% on)
		     //  (128 = 50 % duty cycle, etc.)

If you do this, the PIC will automatically turn pin RC2 on and off at the specified duty cycle and frequency in the background, without you having to keep track of any of the timing yourself!


  1. Write code, using delay_us(), to flash the LED at a frequency of 50kHz (20us period), with a 50% duty cycle. Verify on an oscilloscope that the LED is flashing at the correct frequency.
  2. Write code using the PWM functions mentioned above to do the same thing. Verify the LED's behavior using an oscilloscope.
  3. Write code to make the LED fade in and out, from off to full brightness.
  4. (extra) As in part (3), but make the LED's brightness linear with time (ask us to explain this in more detail).
  5. Use the PWM pin to drive a buzzer. You'll probably want to run the buzzer at 50% duty cycle, so you're just using the PWM generator as an easy way to generate a particular frequency, to make different tones. Hook up the buzzer using this sketchy schematic:
            | |
    PIC ----| |-----[ BUZZER ]--+
    RC2     | |                 |
         100 uF	            |
         capacitor             GND

  6. (extra) Tired of generating only three notes? (And high-pitched squeals, at that). Here are some things you can change:
    1. Replace the 20 MHz crystal with a slowed one, perhaps 4 MHz. Remember to change the #USE (DELAY=) line in your code to reflect this. With this lower frequency oscillator, the base PWM frequencies will also all be a factor of 5 lower, so you can generate a 250 Hz signal with the PWM generator, a reasonably low pitch (though you still only have three tones you can easily generate). You may also need to change the #fuses option HS (high-speed crystal) to something else to get this to work... look in the manual and ask us if you have questions.
    2. You could implement your own oscillations using delay_us or some similar method to get a full range of frequencies, but this is annoying to program and integrate with other code.
    3. You can actually vary the frequency of the PWM generator in much finer increments by changing the "255" in the SETUP_TIMER_2 command mentioned above. The drawback of this is that you lose duty cycle resolution. If you change the 255 to 128, the frequency will double, but you can only pass SET_PWM1_duty numbers between 0 and 128, where 128 will mean 100% on... so now you only have 7 bits of duty cycle resolution instead of 8. But if you're just using this to drive a buzzer at 50% duty cycle, this is no problem, and you now have access to a much wider range of frequencies (all 1.25 kHz and above, though, unless you combine this with the low-frequency-crystal modification mentioned above).

(maxdavis@mit.edu, 2/25/2001)