You are on page 1of 42

© Gooligum Electronics 2013 www.gooligum.com.

au

Introduction to PIC Programming


Programming Mid-Range PICs in C

by David Meiklejohn, Gooligum Electronics

Lesson 12: CCP, part 2 – Pulse-Width Modulation

Mid-range assembler lesson 18 introduced the mid-range Enhanced Capture/Compare/PWM (ECCP)


module’s pulse-width modulation (PWM) modes 1.
We saw that PWM can be used as if it was an analog output, allowing us to control the average voltage (and
power) supplied to a load. It works by rapidly toggling one or more outputs with a variable duty cycle – as
the duty cycle increases, so does the average delivered power.
This lesson revisits that material, showing how to use C to control and access the ECCP module’s PWM
modes, re-implementing the examples using Microchip’s XC8 compiler2 (running in “Free mode”).
In summary, this lesson covers:
 Introduction to the ECCP module’s pulse-width modulation modes, including
o single output
o half-bridge output
o full-bridge output (forward and reverse)
The examples include:
 Dimming an LED
 Generating audible tones (illustrated with a piezo speaker)
 Speed and direction control of a brushed DC motor

Note that the DC motor control examples in this lesson require some components not supplied with the basic
Gooligum mid-range PIC training board.
They are detailed in Appendix A, and are available as a kit of parts from www.gooligum.com.au.

PIC16F684 ECCP Module Pulse-Width Modulation (PWM) Mode


As we saw in the previous lesson, the PIC16F684 includes a single ECCP module3.

1
some older mid-range PICs include a standard Capture/Compare/PWM (CCP) module, which does not provide all the
features of the enhanced version available on the PIC16F684
2
Available as a free download from www.microchip.com.
3
many larger PICs include multiple ECCP and/or CCP modules

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 1


© Gooligum Electronics 2013 www.gooligum.com.au

It is controlled by the CCP1CON register:

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


CCP1CON P1M1 P1M0 DC1B1 DC1B0 CCP1M3 CCP1M2 CCP1M1 CCP1M0

The CCP1M<3:0> bits select the operating mode.


Clearing CCP1M<3:0> turns off and resets the ECCP module.

Setting the CCP1M<3:2> bits to ‘11’ selects PWM mode.


The CCP1M<1:0> bits are then used to configure the PWM output(s), as described below.

PWM output modes


Four PWM output modes are available:
 Single output mode4 is the simplest, where a single PWM signal is output on the CCP1 pin, also
known as P1A5.
 In half-bridge output mode, the PWM signal is output on P1A, and the inverse of the PWM signal is
output on P1B6. It is typically used to drive “push-pull” loads, where the current through the load
changes direction between the “on” and “off” phases of the PWM signal.
 The two full-bridge output modes are intended to drive an “H-bridge” (see the example, later) with a
load, typically a brushed DC motor, that can be driven in “forward” or “reverse” direction. In these
modes, all four PWM outputs, P1A – P1D7, are used.

The PWM output mode is selected by the P1M<1:0> bits, as shown in the table below:

P1M<1:0> PWM output mode CCP1/P1A P1B P1C P1D


00 single modulated – – –
10 half-bridge modulated modulated – –
01 full-bridge forward active inactive inactive modulated
11 full-bridge reverse inactive modulated active inactive

We’ll look at each output mode in more detail, later.

The pin(s) used in each PWM output mode must be configured as outputs by clearing their associated TRIS
bits – for example, on the 16F684 we need to clear TRISC<5> to enable the P1A (same pin as RC5) output.
In the single and half-bridge output modes, some pins, marked with ‘–’ in the table above, are not used as
PWM outputs and are therefore available for use as general I/O pins.

4
this is the only PWM output mode available on the (non-enhanced) CCP module
5
CCP1/P1A is shared with RC5 on the PIC16F684
6
P1B is shared with RC4 on the PIC16F684
7
P1C is shared with RC3 and P1D is shared with RC2 on the PIC16F684

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 2


© Gooligum Electronics 2013 www.gooligum.com.au

Depending on the circuitry connected to the PWM output, the load or switch might be “activated” or “on”
when the PWM output is driven high, in which case we say that the output is active-high. Or, the load or
switch might be activated by a low PWM output, which we would refer to as active-low.
The CCP1M<1:0> bits are used to configure the PWM outputs as being active-high or active-low, as shown
in the table below:

CCP1M<3:0> CCP1/P1A P1B P1C P1D


1100 active-high active-high active-high active-high
1101 active-high active-low active-high active-low
1110 active-low active-high active-low active-high
1111 active-low active-low active-low active-low

Note that the PWM outputs are configured in pairs: P1A and P1C always have the same configuration, as do
P1B and P1D. If the combination you need isn’t available, you may need to reconfigure your circuit,
perhaps by adding an inverter before one or more of the load drivers or switches.

PWM period and duty cycle


A single-output, active-high PWM signal looks something like this:

Period

Pulse Width

TMR2 = PR2+1
TMR2 = CCPR1L
TMR2 = 0

As you can see, it is a repeating series of digital pulses, with a specific period or frequency (usually fixed)
and pulse width (usually varying).

The timing of these pulses – their period and width – depends on Timer2 (TMR2).
As explained in more detail in mid-range assembler lesson 16, Timer2 can only be driven by the instruction
clock (period TCY), which is four times slower than the processor clock (period TOSC, frequency FOSC).
Timer2 can optionally be used with a prescaler, with a ratio of 1:4 or 1:16.
TMR2 increments until it matches the value stored in the PR2 register, and then resets to 0 on the next
increment cycle. This means that TMR2 has a period equal to PR2+1.
The PWM period is the same as the Timer2 period.
Hence:
PWM period = (PR2+1) × TCY × (Timer2 prescale ratio)
PWM frequency = FOSC / (4 × (PR2+1) × (Timer2 prescale ratio))

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 3


© Gooligum Electronics 2013 www.gooligum.com.au

For example, if we are using a 4 MHz processor clock and we configure Timer2 with no prescaler and load
249 into PR2:
FOSC = 4 MHz
TOSC = 0.25 µs
TCY = 1 / (Fosc / 4) = 1 µs
prescale ratio = 1
PR2+1 = 249+1 = 250
PWM period = 250 × 1 µs × 1 = 250 µs
PWM frequency = 4 MHz / (4 × 250 × 1) = 4 MHz / 1000 = 4 kHz

Each PWM pulse begins when TMR2 resets to 0.


The PWM output goes into the “active” state (high, if configured as active-high) at the start of the pulse.
It then remains active until TMR2 matches the value stored in the CCPR1L register.
The PWM output is then put into the “inactive” state (low, if configured as active-high), and remains inactive
until TMR2 reaches PR2 and resets on the next increment cycle, starting the next pulse.
Thus, the pulse width, and hence the duty cycle8, is specified by the value in CCPR1L.

But actually, it’s just a little more complicated than that.


TMR2 and CCPR1L are 8-bit registers. Thus, if the duty cycle was only defined by CCPR1L, the PWM
resolution could be no more than eight bits – meaning that no more than 256 different duty cycles would be
available.
To extend the PWM resolution to ten bits, providing for up to 1024 discrete duty cycles, two less-significant
bits have been added to Timer2. These are derived from either the processor clock (FOSC) or the Timer2
prescaler.
These extra timer bits are hidden; you cannot access them. However, it is possible to specify the two bits
which these hidden timer bits must match, to define the end of each pulse.
These two least-significant bits of the “duty cycle” (more properly, the pulse width) are stored in the two
DC1B bits in the CCP1CON register.
This means that the pulse width is actually specified by a 10-bit value consisting of the 8-bit value in
CCPR1L concatenated with the two DC1B bits9.
Hence, the pulse width is given by:
PWM pulse width = (CCPR1L:DC1B) × TOSC × (Timer2 prescale ratio)

The duty cycle can be calculated as:


PWM duty cycle = (CCPR1L:DC1B) / (4 × (PR2+1))

8
duty cycle = pulse width ÷ period
9
this is equivalent to left-shifting the value in CCPR1L two times (multiplying it by four), then adding the DC1B bits

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 4


© Gooligum Electronics 2013 www.gooligum.com.au

For example, if we configure the processor clock and Timer2 as above, and load 100 (decimal) into
CCPR1L and clear the DC1B bits:
CCPR1L:DC1B = 100 (decimal) concatenated with ‘00’ (binary) = 4 × 100 + 0 = 400
PWM pulse width = 400 × 0.25 µs × 1 = 100 µs
PWM duty cycle = 400 / (4 × 250) = 400 / 1000 = 40%

And if the PWM duty cycle is 40%, it means that the load or switch is “active” or “on” 40% of the time, on
average – meaning that 40% of the maximum available power is being delivered to the load.10

Note that when PR2 = 255, the maximum possible duty cycle is 1023 ÷ 1024 = 99.9%.
If you really do need to be able to generate a “100%” output, you need to use a PR2 value less than 255.

Finally, it’s worth noting that the CCPR1L register and DC1B bits are not used directly in the comparison
with TMR2 (and its two hidden less-significant bits). Instead, they are copied to the CCPR1H register
(which is read-only in PWM mode) and a hidden 2-bit latch at the end of each period, and these copied
values are used in the subsequent PWM period.
This is important because it means that you can update the CCPR1L register and DC1B bits at any time,
without worrying about creating output glitches.

This has been a lot of theory to start with! Some examples should help to explain it more clearly…
We’ll look at the various PWM output modes in turn, starting with the simplest and working our way up.

Single-output PWM
In single-output mode, the modulated signal is output on the CCP1/P1A pin.
Although the output is a “square wave”, as illustrated above, we don’t really want the fluctuations between
high and low to be apparent. The intent of PWM output is that these fluctuations are smoothed out and we
only see the average, which, as we’ve seen, is proportional to the duty cycle.
Often this averaging is done by the load, as we’ll see in the LED dimming and motor control examples, later.
However, in our first example we’ll use a simple RC filter to average the PWM output, so that we can see
clearly what’s going on.

Example 1: Analog output


A PWM output can be used to implement a digital-to-analog converter (DAC), outputting an analog voltage
which is proportional to the PWM duty cycle, by filtering out the PWM frequency (and its harmonics),
leaving only the average voltage. This means low-pass filtering it.
Of course, it’s impossible to build a perfect filter; we can never fully remove the AC ripple from the filtered
PWM output. But a simple RC filter, consisting of only a single resistor and capacitor, as shown in the
circuit diagram on the next page, is often adequate. Note however that the output would usually be buffered,
often with an op-amp, to avoid loading the filter and affecting its operation.

10
This is of course the ideal case. In the real world, non-linear factors such as switching losses mean that the relation
between PWM duty cycle and average delivered power will not be linear – but it’s often close.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 5


© Gooligum Electronics 2013 www.gooligum.com.au

The “3dB” frequency – the frequency


at the “knee” in the filter’s frequency
response curve, above which the
signal begins to be appreciable
attenuated, is given by:

Note that only the RC product (R ×


C) matters – if you use a higher
capacitance, you can use a lower
resistance, and vice-versa.

To attenuate the PWM frequency as


much as possible, you need to make
RC as high as possible. On the other
hand, to avoid limiting the bandwidth
of your analog signal, you should
make RC as low as possible.
For example, if you were using the PWM to generate an audio signal, you probably don’t want to attenuate
frequencies above 5 kHz or so.
Choosing appropriate R and C values is a balancing act that gets easier if the PWM frequency is much higher
than the highest frequencies in your intended analog signal. If that’s not possible, you may need to
implement a more elaborate low-pass filter, such as a multi-pole active filter built with op-amps.

With the values shown here, with a PWM frequency of 4 kHz, f3dB = 15.9 Hz.
The AC ripple in the output is only 0.4% of the AC component of the original PWM signal, but our analog
output will be limited to frequencies below 16 Hz or so. That’s not much use if we wanted to generate audio,
but it is fine for applications where a slow response is ok.

If you have the Gooligum training board, you can use the solderless breadboard to connect the supplied 1 kΩ
resistor to CCP1/P1A (pin 5, ‘RC5’ on the 16-pin header) and the 1 µF capacitor to ground (pin 16,
‘GND’), as shown in the circuit diagram. No jumpers on the board need to be closed.

To start with, let’s generate a single, active-high PWM signal with a fixed frequency of 4 kHz and a fixed
duty cycle of 50%.

As usual, we’ll configure the PIC to use the internal RC oscillator, providing the default 4 MHz clock:
/***** CONFIGURATION *****/
// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

This means that the instruction clock, which drives Timer2, will run at 1 MHz, which is fast enough to
comfortably generate a 4 kHz PWM signal, as we’ll see.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 6


© Gooligum Electronics 2013 www.gooligum.com.au

The CCP1 pin (RC5) must be configured as an output:


// configure ports
TRISC = ~(1<<5); // configure PORTC as all inputs
// except RC5 (CCP1 output)

A 4 kHz PWM signal has a period of 250 µs, so to generate a 4 kHz PWM signal, Timer2 must also have a
period of 250 µs.
If we use a 1:1 prescaler, the 1 MHz instruction clock will increment TMR2 every 1 µs. Hence, we need to
set PR2 to 249, so that TMR2 is reset after 249+1 = 250 counts:
// configure Timer2
T2CONbits.T2CKPS = 0b00; // prescale = 1
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 1 us
PR2 = 249; // period = 250 x 1 us = 250 us
// -> PWM frequency = 4 kHz

To generate a 50% duty cycle, we must set the PWM pulse width to 50% of the period = 125 µs.
Recall that the pulse width is specified by CCPR1L:DC1B<1:0>, and that this 10-bit value is the number of
“quarter TMR2 increments” in the pulse width – it has four times the period’s resolution.
For example, if TMR2 increments every 1 µs, a “quarter increment” is 0.25 µs.
To set the pulse width to 125 µs, we need 125 ÷ 0.25 = 500 of these quarter increments.
This means that we must load CCPR1L:DC1B<1:0> with 500, so we have:
CCPR1L = 125 (= 500÷4)
DC1B =0 (= ‘00’ binary)

Perhaps an easier way to look at this is to say that CCPR1L is equal to the whole part of the pulse width
measured in “TMR2 increments”, and the DC1B bits hold any fractional remainder (0 in this case).

Either way, to configure the ECCP module and specify the pulse width, we have:
// configure ECCP module
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 125; // CCPR1L:DC1B<1:0> = 500 -> width = 125 us
// -> PWM duty cycle = 50%

Note that we’re also selecting PWM mode with all active-high outputs (CCP1M = ‘1100’) and single output
mode (P1M = ‘00’) here.
Note also that, although the PIC16F684 data sheet refers to the bitfields within the CCP1CON register as
‘P1M’ and ‘DC1B’, the “pic16f684.h” header file shipped with XC811 are defines them as ‘PM’ and
‘DCB’ within the ‘CCP1CONbits’ structure, as used in the code above. It’s important to always refer to the

11
as of version 1.12

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 7


© Gooligum Electronics 2013 www.gooligum.com.au

header files shipped with your compiler to check the bit or bitfield symbol definitions, because they may
differ from the names used in the data sheet.

With Timer2 running and the PWM configured like this, a 4 kHz signal with 50% duty cycle will appear on
the CCP1 pin, as shown below:
The yellow
trace is the
PWM output
on CCP1, and
the green trace
is the filtered
output.

The output is
almost flat,
with the AC
ripple barely
visible, and is
around 50% of
the peak PWM
signal level.

With the PWM running “by itself”, the main loop has nothing to do:
for (;;)
{
// do nothing
;
}

Complete program
This is how it all fits together:
/************************************************************************
* *
* Description: Lesson 12, example 1a *
* *
* Demonstrates basic single-output PWM (fixed freq and duty cycle) *
* *
* PWM output is 4 kHz, active-high, 50% duty cycle *
* *
*************************************************************************
* *
* Pin assignments: *
* CCP1 = PWM output *
* *
************************************************************************/

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 8


© Gooligum Electronics 2013 www.gooligum.com.au

#include <xc.h>
#include <stdint.h>

/***** CONFIGURATION *****/


// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

/***** MAIN PROGRAM *****/


void main()
{
/*** Initialisation ***/

// configure ports
TRISC = ~(1<<5); // configure PORTC as all inputs
// except RC5 (CCP1 output)

// Setup PWM
// configure Timer2
T2CONbits.T2CKPS = 0b00; // prescale = 1
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 1 us
PR2 = 249; // period = 250 x 1 us = 250 us
// -> PWM frequency = 4 kHz
// configure ECCP module
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 125; // CCPR1L:DC1B<1:0> = 500 -> width = 125 us
// -> PWM duty cycle = 50%

/*** Main loop ***/


for (;;)
{
// do nothing
;
}
}

Specifying different duty cycles


To change the duty cycle, while retaining the same configuration (4 MHz clock), PWM frequency (4 kHz)
and period (250 µs), we only need to update the CCPR1L register and the DC1B bits.

For example, for a 25% duty cycle, the pulse width will be 25% × 250 µs = 62.5 µs.
CCPR1L holds the whole number of TMR2 increments in the pulse width (62), and the DC1B bits represent
the fractional remainder (0.5) as a number of “quarter increments” (0.5 × 4 = 2 = ‘10’ binary).

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 9


© Gooligum Electronics 2013 www.gooligum.com.au

The ECCP configuration becomes:


CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b10; // LSBs of PWM duty cycle = 10
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 62; // CCPR1L:DC1B<1:0> = 250 -> width = 62.5 us
// -> PWM duty cycle = 25%

The resulting
PWM signal
and filtered
output are
shown on the
right.

The output
now sits at
25% of the
peak PWM
signal level.

For a 75% duty cycle, the pulse width will be 75% × 250 µs = 187.5 µs.
The whole number of TMR2 increments (1 µs each) is 187, so CCPR1L = 187.
The 0.5 µs remainder is 2 × quarter increments (0.25 µs each), so DC1B<1:0> = 2 (= ‘10’ binary).
The ECCP configuration is then:
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b10; // LSBs of PWM duty cycle = 10
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 187; // CCPR1L:DC1B<1:0> = 750 -> width = 187.5 us
// -> PWM duty cycle = 75%

What about a 0% duty cycle, where there is no pulse at all (meaning that the load will be fully “off” or
“inactive”)?

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 10


© Gooligum Electronics 2013 www.gooligum.com.au

That’s easy – there are no TMR2 increments, and no remainder, so CCPR1L = 0, DC1B<1:0> = 0, and we
have:
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 0; // CCPR1L:DC1B<1:0> = 0 -> width = 0 us
// -> PWM duty cycle = 0%

Of course, if you really wanted nothing more than a “0% duty cycle”, you’d just configure RC5 to output a
low, and you wouldn’t bother setting up the PWM at all. But this demonstrates that the PWM duty cycle can
be reduced all the way to 0%, if necessary.

Similarly, what if you needed a 100% duty cycle, where the output (and hence the load) is always active?
All you need to do is make the pulse width equal to12 the period.
In this case, the pulse width will be 100% × 250 µs = 250 µs.
The whole number of TMR2 increments (1 µs each) is 250, so CCPR1L = 250.
There is no fractional remainder, so DC1B<1:0> = 0 (= ‘00’ binary).
The ECCP configuration is then:
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 250; // CCPR1L:DC1B<1:0> = 1000 -> width = 250 us
// -> PWM duty cycle = 100%

Again, if your program did nothing more than output a “100% active” signal, you’d simply configure RC5 to
output a high, and you wouldn’t bother with PWM. But this shows that the PWM duty cycle can be
increased all the way to 100% – except, as noted earlier, in the case when PR2 = 255, in which case the
highest duty cycle you can achieve is 99.9%.

The table on the right shows the actual output Duty cycle Vout (actual) % max Vout
voltage measured for each duty cycle.
0% 0.000 V 0.0%
With a 5 V supply, the maximum output,
corresponding to a 100% cycle is 4.833 V. The 25% 1.206 V 25.0%
column on the far right shows the measured output 50% 2.414 V 49.9%
voltage as a percentage of this maximum.
75% 3.623 V 75.0%
As you can see, the actual output is within 0.1% of
the expected value for every duty cycle, 100% 4.833 V 100.0%
demonstrating that our DAC is really quite accurate!

12
or greater than, but having a pulse width longer than the period doesn’t make any sense…

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 11


© Gooligum Electronics 2013 www.gooligum.com.au

Specifying different frequencies


To change the PWM frequency (or period), while retaining the same configuration (4 MHz clock), we can
use a prescaler to make each TMR2 increment longer and/or use a different PR2 value.
For example, for a 1 kHz PWM we need a TMR2 period of 1000 µs.
If we select the 1:4 prescaler, TMR2 will increment every 4 µs.
Loading PR2 with 249 then gives a TMR2 period of (249+1) × 4 µs = 1000 µs.
Our Timer2 setup then becomes:
T2CONbits.T2CKPS = 0b01; // prescale = 4
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 4 us
PR2 = 249; // period = 250 x 4 us = 1000 us
// -> PWM frequency = 1 kHz

The period is still 250 × TMR2 increments – it’s just that each increment is now 4 µs instead of 1 µs.
This means that, because the pulse width is specified in “TMR2 increments”, the duty cycle calculations are
exactly the same as before.
For example, to generate a 25% duty cycle, the ECCP configuration is:
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b10; // LSBs of PWM duty cycle = 10
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 62; // CCPR1L:DC1B<1:0> = 250 -> width = 250 us
// -> PWM duty cycle = 25%

This is exactly the same as in the 4 kHz example, above.

For a 250 Hz PWM, we need a TMR2 period of 4000 µs.


With the 1:16 prescaler, TMR2 will increment every 16 µs.
Loading PR2 with 249 then gives a TMR2 period of (249+1) × 16 µs = 4000 µs.
Our Timer2 setup is then:
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
PR2 = 249; // period = 250 x 16 us = 4000 us
// -> PWM frequency = 250 Hz

We still have 250 × TMR2 increments and so the duty cycle calculations remain the same as before.
Note that, as we saw in mid-range assembler lesson 16, to select the 1:16 prescaler, we only need to set the
T2CKPS1 bit – there is no need to specify the whole T2CKPS bitfield.
And once again, for a 25% duty cycle, our ECCP configuration is the same as before:
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b10; // LSBs of PWM duty cycle = 10
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high
CCPR1L = 62; // CCPR1L:DC1B<1:0> = 250 -> width = 250 us
// -> PWM duty cycle = 25%

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 12


© Gooligum Electronics 2013 www.gooligum.com.au

The resultant 250 Hz PWM signal, with a duty cycle of 25%, is shown below:
The yellow
trace is the
PWM output
on CCP1, and
the green trace
is the filtered
output.
With this
much ripple in
the output, it is
now very
obvious that
our RC filter is
inadequate for
use with a 250
Hz signal – the
R and/or C
values should
be increased to
compensate for
the lower
frequency.

Example 2: Variable frequency audio output


If a high PWM frequency is used, the analog output technique in the last example can be used to generate
arbitrary waveforms and high-quality audio. However, doing so is quite complex13 and requires significant
processor resources. Using the PWM output as a DAC is overkill if you only need to produce a simple beep.
A single PWM output can be used directly to
generate simple audio tones, as shown on the
right, where a piezo speaker is connected to the
CCP1 PWM output.
In theory, a resistor should be placed between the
PIC and the piezo, to limit inrush current due to
the piezo’s capacitance. A filter could be added to
soften the sound (reduce the harmonics associated
with the square wave output). And of course it’s
possible to use an audio amplifier and speaker
instead of the piezo. But in practice a directly-
connected piezo speaker like this works ok and if
you only need to generate beeps it’s quite
adequate.

We’ll drive the piezo with a variable frequency


controlled by the potentiometer connected to AN0.

13
a topic for a future tutorial?

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 13


© Gooligum Electronics 2013 www.gooligum.com.au

If you have the Gooligum training board, you can implement this circuit by placing a shunt across pins 1 and
2 (‘POT’) of JP2414, connecting the 10 kΩ pot (RP2) to AN0, and placing another shunt across pins 1 and 2
(‘GND’) of JP23, connecting the piezo speaker to ground.

Recall that the PWM frequency, and hence period, depends on the Timer2 period, which in turn depends on
the processor clock frequency, the Timer2 prescaler, and the value in the PR2 register. So, assuming that
the processor clock and Timer2 configuration remain the same, to the PWM frequency will change if the
value in PR2 changes.
Given a 4 MHz processor clock and the 1:16 Timer2 prescaler, the lowest PWM frequency (PR2 = 255) is
244 Hz, while the highest PWM frequency (PR2 = 0) is 62.5 kHz.
That’s not a great match for the range of human hearing, but most piezo speakers, such as the one on the
Gooligum training board, are ineffective at low frequencies, so the 244 Hz – 62.5 kHz range actually works
well in practice (you just won’t be able to hear the highest frequencies…).

Since we’ll be using a 1:16 prescaler, we can configure Timer2 with:


T2CONbits.TOUTPS = 0; // postscale = 1:1
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us

We’re explicitly selecting the 1:1 postscaler (even though 1:1 is the default), because we’ll be using the
TMR2IF flag to wait for the start of a new PWM cycle – see below.

We don’t want to load PR2 with a fixed value – it has to vary (varying the PWM frequency) as the
potentiometer is adjusted.

The easiest way to do that is to use the ADC (see lesson 8) to read the analog voltage on AN0, and, in the
main loop, continually copy the most significant eight bits of the ADC result to PR2.
This means that we need to configure AN0 as an analog input:
TRISC = ~(1<<5); // configure PORTC as all inputs
// except RC5 (CCP1 output)
ANSEL = 1<<0; // make AN0 (only) analog

The ADC is then configured for use with a 4 MHz processor clock and with the most significant eight bits of
the 10-bit ADC result in the ADRESH register:
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)
ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

Within the main loop we can then update the PWM period by simply copying the most significant eight bits
of the ADC result from ADRESH to PR2:
// set new PWM period
PR2 = ADRESH; // PWM period = high byte of ADC result + 1

14
or place the shunt across pins 2 and 3 (‘LDR1’) of JP24 to make a light-controlled musical instrument!

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 14


© Gooligum Electronics 2013 www.gooligum.com.au

However, before updating the PWM period, we should wait for the end of the current PWM cycle, which
occurs when Timer2 overflows. Recall (from lesson 10) that, if the Timer2 postscale ratio is set to 1:1, the
TMR2IF interrupt flag will be set, every time Timer2 overflows.
So, to wait for the end of the current PWM cycle, we can use:
// wait for end of PWM period
PIR1bits.TMR2IF = 0; // clear Timer2 interrupt flag
while (!PIR1bits.TMR2IF) // then wait for it to go high
;

For a fixed duty cycle (for simplicity, let’s say 50%), we need to calculate a new pulse width whenever we
update the period.

Recall that the PWM period, measured in “TMR2 increments”, is given by PR2+1.
If we declare a variable to store the period:
uint16_t period; // PWM period (= PR2+1)

we can then calculate the period using:


period = PR2+1;

Note that the ‘period’ variable is declared as an unsigned 16-bit integer. We can’t use an 8-bit variable,
because PR2 is an 8-bit register: if PR2 = 255 and we add 1 to it, we want to get 255+1 = 256. If the result
had to be truncated to fit into an 8-bit variable, we’d get 255+1 = 0, which is not what we want!
To some extent C makes our lives easy by hiding complexities relating to variable and register size (in the
corresponding example in mid-range assembler lesson 18, we had to handle “PR2 = 255” as a special case,
to avoid this overflow problem). But you do need to be aware of these issues, and ensure that your variables
are large enough to store every possible value, to avoid potentially difficult-to-find bugs!

For a 50% duty cycle, the pulse width is always half the period.
Since CCPR1L holds the whole part of the pulse width, we have simply:
CCPR1L = period/2;

But what if the period doesn’t divide evenly by two? As a whole number of increments, to be represented
accurately, the pulse width would have to have a fractional part – and that’s what the DC1B bits are for.
We don’t need to worry about quarter-increments, so we can leave DC1B<0> = 0.
If the period is divides evenly by two, there will be no half-increment, so DC1B<1> = 0.
However, if the period is odd, the pulse width will need to include a half-increment, so DC1B<1> = 1.
That is, DC1B<1> should be set to the remainder after dividing the period by two, and for that we can use
the modulus operator:
CCP1CONbits.DC1B1 = period%2;

So, to set the duty cycle to 50%, we have:


period = PR2+1;
CCPR1L = period/2; // CCPR1L:DCB<1:0> = (period x 4) / 2
CCP1CONbits.DC1B1 = period%2; // -> PWM duty cycle = 50%

The C language makes this a lot simpler that the corresponding example in mid-range assembler lesson 18!

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 15


© Gooligum Electronics 2013 www.gooligum.com.au

Complete program
This is how all these pieces fit together:
/************************************************************************
* *
* Description: Lesson 12, example 2 *
* *
* Demonstrates varying single-output PWM frequency (fixed duty cycle) *
* *
* Sounds piezo on CCP1 at 244 - 62500 Hz, *
* with frequency derived from an analog input *
* *
*************************************************************************
* *
* Pin assignments: *
* CCP1 = PWM output (e.g. piezo speaker) *
* AN0 = analog input (e.g. pot or LDR) *
* *
************************************************************************/

#include <xc.h>
#include <stdint.h>

/***** CONFIGURATION *****/


// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

/***** MAIN PROGRAM *****/


void main()
{
uint16_t period; // PWM period (= PR2+1)

/*** Initialisation ***/

// configure ports
TRISC = ~(1<<5); // configure PORTC as all inputs
// except RC5 (CCP1 output)
ANSEL = 1<<0; // make AN0 (only) analog

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)
ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// configure Timer2
T2CONbits.TOUTPS = 0; // postscale = 1:1
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
// configure ECCP module
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 16


© Gooligum Electronics 2013 www.gooligum.com.au

CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00


CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high

/*** Main loop ***/


for (;;)
{
// sample analog input
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.nDONE) // wait until done
;

// wait for end of PWM period


PIR1bits.TMR2IF = 0; // clear Timer2 interrupt flag
while (!PIR1bits.TMR2IF) // then wait for it to go high
;

// set new PWM period


PR2 = ADRESH; // PWM period = high byte of ADC result + 1

// set duty cycle to 50%


period = PR2+1;
CCPR1L = period/2; // CCPR1L:DCB<1:0> = (period x 4) / 2
CCP1CONbits.DC1B1 = period%2; // -> PWM duty cycle = 50%
}
}

When you try this program, you will find that the frequency produced by the piezo speaker (or whatever
you’re using for audio output) will vary as the voltage on AN0 varies, although any noise in the analog input
will result in the output tone not sounding “pure”, especially at higher frequencies. This could be reduced by
filtering (smoothing) the analog input, whether via hardware or software (see lesson 8).

Example 3: LED dimming


PWM is useful for dimming LEDs.
The perceived brightness of an LED depends on the current passing through it. LEDs are non-linear devices,
making it impractical to control an LED’s brightness by varying the voltage across it. Instead, it’s usually
much easier to use a PWM signal to control (switch on and off) a constant current source – which can be as
simple as a fixed voltage and a current-limiting resistor. When a number of LEDs are to be dimmed together
(all will have the same brightness), it’s common to arrange the LEDs in series, in a “string”, with a high-
enough voltage across the string. The current in all the LEDs is then the same, and can be controlled via a
single switch, such as a MOSFET, which is in turn controlled by a PWM signal.
PWM dimming works by flashing the LED(s) on and off so quickly (above 60 Hz or so) that human
persistence of vision ensures that the flashing isn’t visible; the eye only perceives the average brightness,
which varies linearly with the PWM duty cycle.
Importantly, there is no need for an RC filter circuit, as we used in example 1.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 17


© Gooligum Electronics 2013 www.gooligum.com.au

Instead, if we’re only dimming a single LED15, we


can connect it directly to the PWM output (CCP1),
as shown in the circuit diagram on the right.
We’ll drive the LED with a variable duty cycle
controlled by the potentiometer connected to AN0.

If you are using the Gooligum training board, you


will see that it does not include an LED directly
connected to RC5 (CCP1). That’s not really a
problem though – it’s easy to connect one of the
on-board LEDs to RC5, via the expansion header.
If you have the Gooligum board, place a shunt
across pins 1 and 2 (‘POT’) of JP2416, connecting
the 10 kΩ pot (RP2) to AN0. Close jumper JP19 to
enable the LED on RC3, and place a wire link
between pins 5 (‘RC5’) and 7 (‘RC3’) of the 16-pin
expansion header. With this link in place, the LED
labelled ‘RC3’ will light whenever the CCP1
PWM output goes high.

Recall that the duty cycle is given by:


PWM duty cycle = (CCPR1L:DC1B) / (4 × (PR2+1))
and that, if we clear the DC1B bits, we have simply:
PWM duty cycle = CCPR1L / (PR2+1)

For the highest PWM resolution, where the duty cycle can be adjusted in the smallest-possible increments,
we should make the value of PR2 as high as possible, which, because it’s an 8-bit register, is 255.

With PR2 = 255, and the DC1B bits clear, we have:


PWM duty cycle = CCPR1L / 256
CCPR1L is an 8-bit register, so that gives us eight bits of resolution.
If we need the full (maximum-possible) 10-bit resolution, we can also make use of the DC1B bits:
PWM duty cycle = (CCPR1L:DC1B) / 1024

Keep in mind that, if you increase the PWM frequency by making the value in PR2 smaller, you will
decrease the PWM duty cycle resolution.
We want to demonstrate the full range of duty cycles, so we’ll make PR2 = 255.

15
As mentioned, if you were dimming a number of LEDs, you would normally arrange them in string, switched by a
series MOSFET, in a similar way to the motor control example, below.
16
or, if you place the shunt across pins 2 and 3 (‘LDR1’) of JP24, the LED will be controlled by photocell PH1

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 18


© Gooligum Electronics 2013 www.gooligum.com.au

With our usual 4 MHz clock, and no Timer2 prescaler, TMR2 will increment every 1 µs:
// configure Timer2
T2CONbits.T2CKPS = 0b00; // prescale = 1
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 1 us

With PR2 = 255 the TMR2 period is then 256 × 1 µs = 256 µs, and the PWM frequency is 3906 Hz17:
PR2 = 255; // period = 256 x 1 us = 256 us
// -> PWM frequency = 3906 Hz

This is more than fast enough to avoid the LED perceptibly flickering.

We don’t really need more than eight bits of PWM duty cycle resolution (can you notice a difference of less
than 0.4% in an LED’s brightness?), so we can ignore the DC1B bits, and leave them cleared:
// configure ECCP module
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single output (CCP1) mode, active-high

Since we only need eight bits of resolution, we only need the most significant eight bits of the ADC result, so
we can configure the ADC with:
// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)
ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

Whenever we sample AN0, the 8-bit ADC result will now appear in ADRESH.
To update the duty cycle, we can simply copy it to CCPR1L, within the main loop:
// set new PWM duty cycle
CCPR1L = ADRESH; // PWM duty cycle
// = high byte of ADC result / 256

Note that, if we did need the full 10-bit PWM resolution, we could extract the two least significant bits of the
ADC result from ADRESL, and copy them into the DC1B bits in CCP1CON.
But for this application there’s really no need to go to that extra effort18.

17
At power-on, the value in PR2 is 255 by default, so strictly-speaking there is no need to load 255 into PR2 like this.
But it’s good practice to explicitly do so, in case you want to change the PWM frequency later.
18
although, when programming in C, it’s fairly easy to do this – you might want to try it, as an exercise.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 19


© Gooligum Electronics 2013 www.gooligum.com.au

Complete program
Here is the complete source code, showing how these fragments fit together:
/************************************************************************
* *
* Description: Lesson 12, example 3 *
* *
* Demonstrates varying single-output PWM duty cycle *
* *
* Outputs PWM signal (~3906 Hz) on CCP1 *
* with duty cycle derived from an analog input *
* *
*************************************************************************
* *
* Pin assignments: *
* CCP1 = PWM output (e.g. LED or motor) *
* AN0 = analog input (e.g. pot or LDR) *
* *
************************************************************************/

#include <xc.h>
#include <stdint.h>

/***** CONFIGURATION *****/


// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

/***** MAIN PROGRAM *****/


void main()
{
/*** Initialisation ***/

// configure ports
TRISC = ~(1<<5); // configure PORTC as all inputs
// except RC5 (CCP1 output)
ANSEL = 1<<0; // make AN0 (only) analog

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)
ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// configure Timer2
T2CONbits.T2CKPS = 0b00; // prescale = 1
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 1 us
PR2 = 255; // period = 256 x 1 us = 256 us
// -> PWM frequency = 3906 Hz
// configure ECCP module
CCP1CONbits.PM = 0b00; // select single output mode
// -> CCP1 active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 20


© Gooligum Electronics 2013 www.gooligum.com.au

CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high


// -> single output (CCP1) mode, active-high

/*** Main loop ***/


for (;;)
{
// sample analog input
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.nDONE) // wait until done
;

// set new PWM duty cycle


CCPR1L = ADRESH; // PWM duty cycle
// = high byte of ADC result / 256
}
}

When you try this program, you may find that with the trimpot on the Gooligum training board fully counter-
clockwise, the LED still glows very faintly, appearing to flicker a little. This is due to noise in the analog
input, which, as in the previous example, could be reduced through hardware or software filtering – or
perhaps by modifying the “set new PWM duty cycle” routine so that, if the ADC result is below some
threshold, the duty cycle is set to zero.

Example 4: Simple unidirectional brushed DC motor control


One of the most common uses of PWM is to control electric motors. Although PWM can be (and is) used in
sophisticated ways to control many types of motor, including brushless DC, synchronous and induction AC
motors, we’ll only demonstrate simple brushed DC motor control in this lesson.
A brushed DC motor is a relatively simple device: place a DC voltage across it, and it will (attempt to) spin.
A higher voltage will result in a higher current, increasing the motor’s torque, making it spin faster.
Increasing the load makes the motor spin slower, and if the voltage remains constant, it will draw more
current, providing additional torque in response to the increased load. And reversing the voltage reverses the
motor’s direction of spin.
A motor has inertia and cannot respond quickly to power supply changes. If we drive a DC motor with a
PWM signal with a “high enough” frequency signal, the motor’s inertia will effectively average the motor’s
response, and it will spin smoothly, with its speed increasing with the PWM duty cycle. Of course “high
enough” depends on the motor, but quite low PWM frequencies, down to 100 Hz or lower, can be effective
with most DC motors. However, using PWM to drive a motor can lead to an audible hum, or whine, at the
PWM frequency, so it’s common to use a drive frequency of 4 kHz or more, to shift this audible whine
above the range of most human hearing.

It would be rare to find a motor that can operate with less than the 25 mA that one of the PIC16F684’s pins
can supply. Indeed, even the tiny motor supplied in the Gooligum training board motor control kit can draw
more than 150 mA. And although that motor will work fine with a 5 V supply, many DC motors are
designed for 12 V or 24 V operation. This means that driving a DC motor isn’t as simple as connecting it
directly to a PIC pin, as we were able to do with the LED in the example above. Instead, we need to use
some type of driver, or external switch.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 21


© Gooligum Electronics 2013 www.gooligum.com.au

A straightforward approach is to use a driver chip, such as the SN754410 (a pin-compatible, but improved,
version of the venerable L293D), as shown in the circuit below:

As we did in the last example, we can use the potentiometer connected to AN0 to control the PWM duty
cycle, which will in turn control the speed of the motor.
And as you can see, we don’t need any components other than a 754410 or L293D chip to drive the motor.

The “input” side of the 754410 is intended to connect directly to “5 V” digital logic, the input circuitry
having its own supply (VCC1), separated from the output circuits.
The motor can be driven directly by one of the outputs. The 754410 (like the L293D) includes internal
diodes on its outputs which clamp the voltage spikes generated by inductive loads (such as motors) when the
voltage across them changes suddenly, avoiding the need for an external flyback diode. That’s important
when using PWM, which by its nature involves sudden on/off voltage changes, to drive a motor.
The voltage delivered to the motor is derived from the “output” power supply connected to VCC2, which can
be up to 36 V. Although the same voltage is used for the input and output supplies in this example, you’d
typically use a separate supply on the output side, allowing for a higher motor voltage and, importantly,
isolating the output from the PIC’s power supply.
If you do use a single 5 V supply for the digital and output circuitry (as shown below), you must use an
external well-regulated power supply, connected via your training or development board or directly to the
breadboard. Do not use a PICkit 2 or PICkit 3 alone to power the whole circuit.
Adding a 1 µF ceramic bypass capacitor (supplied with your Gooligum training board) will help avoid high-
frequency noise feeding back from the motor to the power supply, potentially causing problems for the PIC,
which runs off the same supply.
However, to avoid problems it’s better to use a separate supply on the output side – even a 4.5 V battery
pack will do. It is then ok to use a PICkit 2 or PICkit 3 to power the logic circuits, including the PIC.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 22


© Gooligum Electronics 2013 www.gooligum.com.au

You can build this circuit with the Gooligum training board, using the SN754410 IC and motor supplied in
the motor control kit – connecting them to signals on the 16-pin header: CCP1/P1A on pin 5 (‘RC5’), +5 V
and ground on pins 15 (‘+V’) and 16 (‘GND’), using the solderless breadboard, as illustrated below.

You also need to place a shunt across pins 1 and 2 (‘POT’) of JP24, connecting the 10 kΩ pot (RP2) to AN0.

Note that the motor supplied in the motor control kit may be different to that shown, and that you may need
to solder some solid-core wire (supplied with the kit) to the motor to connect it to the breadboard, or use
crocodile clip leads.

Also note that a piece of foam has been pushed onto the motor spindle, to make it easier to see it spin.

An alternative approach is to use an N-channel logic level MOSFET switch, as in the circuit shown on the
next page.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 23


© Gooligum Electronics 2013 www.gooligum.com.au

Almost any N-channel


MOSFET rated for the
motor current and voltage
can be used, but it must be
a logic level device, such
as the PSMN022-30PL
supplied in the motor
control kit.
When the PIC’s
CCP1/P1A output goes
high, the MOSFET
conducts, turning on the
motor. The 220 Ω resistor
limits the in-rush current
due to the MOSFET’s gate
capacitance, while the 10
kΩ resistor ensures that it
remains off while the
PWM output is in its initial
high-impedance state.
The “flyback” or “freewheeling” diode across the motor protects the MOSFET from the high voltage spike
generated by the motor’s inductance when the MOSFET turns off.
The motor-control components can be breadboarded as illustrated below:
The connections to the 16-
pin header on the
Gooligum training board
are the same as before,
with the 220 Ω and 10 kΩ
resistors connecting to pin
5 (‘RC5’), and +5 V and
ground to pins 15 (‘+V’)
and 16 (‘GND’).
You must also place a
shunt across pins 1 and 2
(‘POT’) of JP24,
connecting the 10 kΩ pot
(RP2) to AN0.

Once again, although the


same voltage is used for
the digital and motor
power supplies in this
example, you’d typically
use a separate supply on
the motor side, which is
why different power
supply symbols are used in
the digital and motor
sections of the circuit
diagram.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 24


© Gooligum Electronics 2013 www.gooligum.com.au

If you do choose to use a single 5 V supply for the motor as well as the digital logic circuitry, as illustrated
here, note again that you must use an external well-regulated power supply, instead of using a PICkit 2 or
PICkit 3 to power the whole circuit.

It’s also possible to use a P-channel MOSFET as a “high-side” switch, between the motor and the positive
supply. As we’ll see in the full-bridge example later, that’s easy enough to do with a logic level MOSFET,
such as the NDP6020P supplied in the motor control kit – as long as the motor power supply is 5 V. If you
need to drive a higher-voltage motor, a high-side switch requires additional level-shifting circuitry to drive
the MOSFET – something which is not necessary with low-side N-channel MOSFETs. Additionally, P-
channel MOSFETs tend to be more expensive than equivalent N-channel devices. For these reasons, a low-
side switch, as in the circuit above, is more commonly used for an application like this.

Whether you use the SN754410 driver or the N-channel MOSFET, the method of PWM control is exactly
the same: a “high” on the CCP1/P1A pin will turn the motor on, a “low” turns it off, and increasing the
PWM duty cycle will deliver more power to the motor.
In fact, this is exactly the same as in the LED dimming example, above. A higher duty cycle means more
light from the LED, or more speed from the motor. Only the circuit is different.
So, whichever motor control circuit you use, if you try the program from example 3, you will find that the
motor speed varies from “stopped” to “full speed” as you turn the potentiometer.

However, despite the earlier comment that it’s common to use a PWM frequency of 4 kHz or higher to avoid
an audible whine from the motor, the motor supplied with the motor control kit for the Gooligum training
board may perform better with a lower drive frequency.

We can easily lower the PWM frequency by using the Timer2 prescaler.
Selecting the 1:16 prescaler increases the TMR2 period by 16 times, to 4096 µs:
// configure Timer2
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
PR2 = 255; // period = 256 x 16 us = 4096 us
// -> PWM frequency = 244 Hz

There is no need to list the rest of the program; it is the same as in example 3.

With this change, the PWM frequency is reduced to 244 Hz, and you may find that you now have better
control at low speeds (the motor stops spinning at a lower duty cycle than before).

Of course, every motor is different, and you may need to do some testing to find the optimal drive frequency
for your motor and control circuitry (e.g. switching losses will increase as the PWM frequency increases).

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 25


© Gooligum Electronics 2013 www.gooligum.com.au

Half-bridge output mode


In half-bridge mode, the PWM signal is output on P1A, while a complementary signal is output on P1B.
When the P1A output is “active”, P1B is “inactive”, and vice-versa – if both outputs are configured as
active-high, P1A will be high whenever P1B is low.

Half-bridge output mode is so named because it can be used to drive


a half-H19 or half-bridge circuit, as illustrated on the right:
The P1A and P1B signals are connected to MOSFETs via drivers.
Although the diagram shows P1A driving a P-channel MOSFET, an
N-channel device could be used instead, with a suitable driver.
Indeed, in principle, any form of electronic or mechanical switch
could be substituted for either MOSFET.
What is important is that, when the P1A signal is active, the upper
MOSFET is switched on, connecting the load to the +V power
supply, while when P1B is active, the lower MOSFET is switched
on, connecting the load to ground.
This means that one side of the load can be actively pulled either
high or low. By comparison, the single MOSFET in the previous
example can only actively pull the load low20.
This arrangement can alternately source or sink current, which is useful when driving “push/pull” loads,
where the direction of current through the load alternates between the “active” and “inactive” phases of the
PWM signal.

But there’s a risk. If both switches are ever “on” at the same time, a (potentially very high) shoot-through
current will flow directly from +V to ground.
This can occur inadvertently, if, when P1A and P1B are flipping between their active and inactive states,
one MOSFET takes longer to switch off than the other takes to switch on. For a brief time, during the
transition, both MOSFETs would be on, and a shoot-through current would flow.
To avoid this, the half-bridge output mode provides a “programmable dead-band delay” mode, where a delay
(called the dead-band) is introduced between one signal switching to inactive and the other becoming active,
giving the “on” switch enough time to fully turn off, before the other switch turns on.
This dead-band delay is set by the PDC<6:0> bits in the PWM1CON register.
However, as we will not be using this half-H driver arrangement in the examples in this section, we will not
make use of or further illustrate the programmable dead-band delay mode in this lesson.
If you do need to use this feature, please refer to the “programmable dead-band delay mode” section of the
data sheet.

19
The reason for the “half-H” name will become apparent when we look at the full-bridge example, later…
20
Each of the four driver circuits in a L293 (or compatible) can actively pull its output high or low, by sourcing or
sinking current, which is why the L293 is referred to as a “quad half-H” driver.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 26


© Gooligum Electronics 2013 www.gooligum.com.au

Example 5: Using half-bridge mode to increase effective PWM voltage


Assuming that the dead-band delay mode is not being used, and assuming that both P1A and P1B are
configured as active-high outputs, the signals present on each will be complementary, as illustrated below:

P1A

P1B

P1A – P1B

If a load is connected directly across the P1A and P1B pins, it will “see” the difference in voltage between
them: P1A – P1B, as shown.
As you can see, the total voltage swing between the two PWM phases is then twice as large as it is for a
single PWM output.

This higher effective voltage can be put to good use


when driving loads such as a piezo speaker, which
produces a louder sound when a higher voltage is
applied across it.

This can be done very simply by connecting the piezo


directly across P1A and P1B, as shown on the right.

To implement this circuit with the Gooligum training


board, simply place a shunt across pins 2 and 3 (‘P1B’)
of JP23, connecting one side of the piezo speaker to
P1B (the other side is permanently connected to P1A.

There is no need for a complicated demonstration here;


we can simply produce a loud “beep”.
The piezo sounder included on the Gooligum training
board has a resonant frequency of 4 kHz, so that’s the
frequency we should use if we want the loudest sound.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 27


© Gooligum Electronics 2013 www.gooligum.com.au

The code is the same as that used in example 1 to generate a 4 kHz PWM output, with 50% duty cycle21,
except that we need to configure both P1A and P1B as outputs:
// configure ports
TRISC = ~(1<<5|1<<4); // configure PORTC as all inputs
// except RC5 and RC4 (P1A, P1B outputs)

and of course we need to set the P1M bits to ‘10’ to select half-bridge output mode when configuring the
ECCP module:
// configure ECCP module
CCP1CONbits.PM = 0b10; // select half-bridge output mode
// -> P1A, P1B active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> half-bridge output mode,
// P1A, P1B active-high

That’s all! The rest of the program is the same as in example 1.


The piezo should now be significantly louder than it was in example 2!

Example 6: Using half-bridge mode for bidirectional brushed DC motor control


Recall that the half-bridge output mode can be used to alternately drive current though a load in different
directions, and that reversing the current through a brushed DC motor reverses its direction of spin. Recall
also that for “high enough” PWM frequencies, the motor’s inertia will effectively average the motor’s
response to a PWM signal driving it.
This means that, when a brushed DC motor is driven by a pair of complementary PWM outputs, via a pair of
half-bridge drivers, both the motor’s direction and speed can be controlled by adjusting the PWM duty cycle.
To demonstrate this, we can use the circuit shown below:

21
You may want to experiment with different duty cycles, to hear how it affects the tone produced.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 28


© Gooligum Electronics 2013 www.gooligum.com.au

Assuming that the PWM outputs are both configured as active-high, during the active phase of the PWM
signal, P1A will be high and therefore the 754410’s ‘1Y’ output will be driven high. At the same time, P1B
will be low, driving the ‘2Y’ output low. Current will flow from 1Y to 2Y, causing the motor to attempt to
turn in the forward direction.
During the inactive PWM phase, P1A will be low and P1B will be high, driving the ‘1Y’ output low and
‘2Y’ high. Current will now flow in the reverse direction, from 2Y to 1Y, causing the motor to reverse.
If the duty cycle is 50%, the PWM output divides its time equally between the active and inactive phases,
and the current flow in the forward direction will be, on average, the same as that in the reverse direction. If
the PWM frequency was low enough, we’d see the motor jitter back and forth, attempting to alternately
move forward and reverse. But if the PWM frequency is high enough, a combination of the motor’s winding
inductance and mechanical inertia will effectively average this alternating forward and reverse current to
zero – the motor won’t move.
With a 100% duty cycle P1A is always high, P1B is always low and current only flows in the forward
direction, from 1Y to 2Y – and the motor will spin forward at maximum speed.
As the other extreme (0% duty cycle), P1A is always low and current only flows in the reverse direction,
from 2Y to 1Y – driving the motor in reverse, at maximum speed.
So we have:
0% duty cycle = full reverse
50% duty cycle = stopped
100% duty cycle = full forward
And because of the averaging effect, the motor speed varies smoothly between these points as the PWM duty
cycle is adjusted.

We can illustrate this with the circuit shown above, using the potentiometer connected to AN0 to control the
PWM duty cycle, as we did in examples 3 and 4.
If you have the Gooligum training board, you can build the circuit in the same way as in example 4,
connecting the SN754410 (or L293D) IC and motor supplied in the motor control kit to signals on the 16-pin
header: P1A on pin 5 (‘RC5’), P1B on pin 6 (‘RC4’), +5 V and ground on pins 15 (‘+V’) and 16 (‘GND’),
via the solderless breadboard. You also need to place a shunt across pins 1 and 2 (‘POT’) of JP24,
connecting the 10 kΩ pot (RP2) to AN0.
And again, ideally you should use a separate supply on the output side. However if you do choose to use a
single 5 V supply for both the output and logic circuitry, ensure that you use an external regulated 5 V power
supply, instead of using a PICkit 2 or PICkit 3 to power the whole circuit.

The program code is essentially the same as that in examples 3 and 4, with only a few differences.
As we did in the last example, we now need to configure both P1A and P1B as outputs:
// configure ports
TRISC = ~(1<<5|1<<4); // configure PORTC as all inputs
// except RC5 and RC4 (P1A, P1B outputs)

In example 4 we used a PWM frequency of only 244 Hz, because the motor supplied with the motor control
kit for the Gooligum training board may perform better with a lower drive frequency. However, when the
motor is driven in push/pull fashion, as in this example, that drive frequency may be too low – it might make
the motor vibrate. Better results may be obtained with a PWM frequency closer to 1 kHz.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 29


© Gooligum Electronics 2013 www.gooligum.com.au

We can easily achieve this by reducing the Timer2 prescaler to 1:4:


// configure Timer2
T2CONbits.T2CKPS = 0b01; // prescale = 4
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 4 us
PR2 = 255; // period = 256 x 4 us = 1024 us
// -> PWM frequency = 977 Hz

And finally of course we need to set the P1M bits to ‘10’ to select half-bridge output mode when configuring
the ECCP module:
// configure ECCP module
CCP1CONbits.PM = 0b10; // select half-bridge output mode
// -> P1A, P1B active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> half-bridge output mode,
// P1A, P1B active-high

Complete program
Although the program is otherwise the same as that in example 3, it’s worth listing it again in full, so that
you can see where these few changes fit in:
/************************************************************************
* *
* Description: Lesson 12, example 6 *
* *
* Demonstrates varying half-bridge output PWM duty cycle *
* *
* Outputs complementary PWM signals (~977 Hz) on P1A and P1B *
* with duty cycle derived from an analog input *
* *
*************************************************************************
* *
* Pin assignments: *
* P1A = normal PWM output *
* P1B = complementary PWM output *
* AN0 = analog input (e.g. pot or LDR) *
* *
************************************************************************/

#include <xc.h>
#include <stdint.h>

/***** CONFIGURATION *****/


// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

/***** MAIN PROGRAM *****/


void main()
{

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 30


© Gooligum Electronics 2013 www.gooligum.com.au

/*** Initialisation ***/

// configure ports
TRISC = ~(1<<5|1<<4); // configure PORTC as all inputs
// except RC5 and RC4 (P1A, P1B outputs)
ANSEL = 1<<0; // make AN0 (only) analog

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)
ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// configure Timer2
T2CONbits.T2CKPS = 0b01; // prescale = 4
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 4 us
PR2 = 255; // period = 256 x 4 us = 1024 us
// -> PWM frequency = 977 Hz
// configure ECCP module
CCP1CONbits.PM = 0b10; // select half-bridge output mode
// -> P1A, P1B active
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> half-bridge output mode,
// P1A, P1B active-high

/*** Main loop ***/


for (;;)
{
// sample analog input
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.nDONE) // wait until done
;

// set new PWM duty cycle


CCPR1L = ADRESH; // PWM duty cycle
// = high byte of ADC result / 256
}
}

Full-bridge output mode


It’s not always appropriate to drive a reversible load in “push/pull” fashion, as we did in the last example.
It’s often more useful to be able to drive a load in either of two directions, with the modulated current
flowing in the same direction throughout the PWM cycle (not alternating back and forth, as it was in the
half-bridge examples), but being capable of being reversed when required.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 31


© Gooligum Electronics 2013 www.gooligum.com.au

A common way of achieving this is with a full-bridge circuit, as illustrated below.

The full-bridge is also referred to as


a full-H, or simply H-bridge, for
obvious reasons.
It consists of four switches (usually,
but not necessarily, MOSFETs),
arranged as shown, allowing each
side of the load to be connected to
either the +V supply or ground.
The P1A, P1B, P1C and P1D
signals are connected to the
MOSFETs via drivers, allowing
each to be switched on, off, or
modulated.

When the ECCP module is configured in full-bridge


forward mode, P1B and P1C are set to their “inactive”
states, and P1A is set to “active”, while P1D is
modulated.
This means that the MOSFETs connected to P1B and
P1C are always off, and the MOSFET connected to P1A
is always on – so, in full-bridge forward mode, the H-
bridge is equivalent to the simple circuit on the right.
Current always flows in the direction shown by the
arrow, and is modulated, in the same way as in single-
output PWM, by the PWM signal on P1D.

When the ECCP module is instead configured in full-


bridge reverse mode, P1A and P1D are set to their
“inactive” states, and P1C is set to “active”, while P1B
is modulated.
Thus, the MOSFETs connected to P1A and P1D are
always off, and the MOSFET connected to P1C is
always on – so, in full-bridge reverse mode, the H-bridge
is becomes equivalent to the simple circuit on the right.
Current now always flows in the reverse direction, as
shown by the arrow, and is modulated by the PWM
signal on P1B.

When driving a brushed DC motor, the H-bridge can be operated as a brake to rapidly stop the motor, by
switching on the two “lower” MOSFETs (connected P1B and P1D) while switching the others off.
The H-bridge is then equivalent to the (very!) simple circuit on the right.
The two sides of the motor are grounded, effectively shorting them.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 32


© Gooligum Electronics 2013 www.gooligum.com.au

Note that “brake mode” is not provided by the ECCP mode; to operate the H-bridge in this way, the ECCP
module should be disabled (by clearing the CCP1M bits) and the PWM outputs configured directly by
setting or clearing RC2, RC3, RC4 and RC5 pins as appropriate.

Example 7: Using full-bridge mode for unidirectional brushed DC motor control


To illustrate the full-bridge PWM modes, we can start by using an H-bridge to drive a brushed DC motor in a
single direction, with the PWM duty cycle (and hence motor speed) controlled by a potentiometer connected
to AN0, as shown below:

This is the same as we did in example 4, except that, instead of a single PWM output, now we’re using a full-
bridge.
The lower half of the H-bridge will be familiar from example 4. Logic-level N-channel MOSFETs, such as
the PSMN022-30PL devices shown here, are connected to P1B and P1D via 220 Ω resistors (used to limit
the gate in-rush current), while 10 kΩ resistors ensure that the MOSFETs remain off while the PWM outputs
are in their initial high-impedance state. If either the P1B or P1D output is set high, the corresponding
MOSFET is switched on. Thus, P1B and P1D should be configured as active-high.
The upper half of the H-bridge uses logic-level P-channel MOSFETs, such as the NDP6020P devices
supplied in the Gooligum motor control kit. It’s a solution that works well in this example, but it does mean
that the motor power supply voltage cannot be higher than the PIC’s supply voltage – otherwise, the PIC’s
PWM outputs would not be able to pull the MOSFET’s gates high enough to turn them off.
So, although you could in principal use a higher voltage on the motor side, allowing the use of a more
powerful motor, you can’t do that with this simple circuit. You’d need to add MOSFET drivers. That’s not
really a problem – using a higher motor drive voltage is such a common requirement that many appropriate
drivers are available, including entire H-bridges with logic-level inputs in a single package. We’ve used
discrete components here to more clearly illustrate the full-bridge modes (you can see the “H” clearly…), but
in a real design you may be more likely to deploy an integrated H-bridge.

With the P-channel MOSFETs connected as shown, if either the P1A or P1C output is driven low, the
corresponding MOSFET is switched on. Thus, P1A and P1C should be configured as active-low.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 33


© Gooligum Electronics 2013 www.gooligum.com.au

Once again the additional 1 µF ceramic bypass capacitor (supplied with the Gooligum training board) helps
to reduce high-frequency switching noise feeding back from the motor control circuit to the power supply.

You can build this circuit with the Gooligum training board, using the MOSFETs, resistors and motor
supplied in the motor control kit using the solderless breadboard, as illustrated below.
You will need to make
connections to the
following pins on the
16-pin header on the
Gooligum training
board:
P1A on pin 5 (‘RC5’),
P1B on pin 6 (‘RC4’),
P1C on pin 7 (‘RC3’),
P1D on pin 11 (‘RC2’),
+5V on pin 15 (‘+V’),
Ground on pin 16
(‘GND’).
You must also place a
shunt across pins 1 and
2 (‘POT’) of JP24,
connecting the 10 kΩ
pot (RP2) to AN0.

And note again that you


would typically use a
separate power supply
for the motor drive
circuitry. If you do use
a single 5 V supply, as
illustrated here, you
must use an external
regulated power supply.

Most of the program code is exactly the same as that used in examples 3 and 4 – the only difference is that
we’re using a different PWM mode.

Since we’re now using the P1A, P1B, P1C and P1D pins for PWM, we have to configure them as outputs:
// configure ports
TRISC = 0b000011; // configure PORTC as all inputs
// except RC2-5 (P1A-D outputs)

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 34


© Gooligum Electronics 2013 www.gooligum.com.au

As we did in example 4, we’ll use a PWM frequency of 244 Hz, because it’s appropriate for the motor
supplied with the Gooligum motor control kit:
// configure Timer2
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
PR2 = 255; // period = 256 x 16 us = 4096 us
// -> PWM frequency = 244 Hz

And of course we have to set the P1M bits to ‘01’ to select full-bridge output forward mode, and the
CCP1M bits to ‘1110’ to configure the P1A and P1C pins as active-low, and P1B and P1D as active-high:
// configure ECCP module
CCP1CONbits.PM = 0b01; // select full-bridge output forward mode
// -> PID modulated, P1A active,
// P1B, PIC inactive
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1110; // select PWM mode: P1A, P1C active-low
// P1B, P1D active-high
// -> full-bridge output forward mode,
// P1D modulated (active-high)
// P1A active (low)
// P1B inactive (low)
// P1C inactive (high)

Alternatively, we could select full-bridge output reverse mode (setting the P1M bits to ‘11’) with:
// configure ECCP module
CCP1CONbits.PM = 0b11; // select full-bridge output reverse mode
// -> PID modulated, P1A active,
// P1B, PIC inactive
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1110; // select PWM mode: P1A, P1C active-low
// P1B, P1D active-high
// -> full-bridge output reverse mode,
// P1B modulated (active-high)
// P1C active (low)
// P1A inactive (high)
// P1D inactive (low)

Since the rest of the code is exactly the same as in example 4, there is no need to list the whole program here.

Example 8: Using full-bridge mode for bidirectional brushed DC motor control


If all only need to run a brushed DC motor in a single direction, there’s little reason to use a full H-bridge;
we might as well use single-output PWM for that.
Full-bridge output mode comes into its own when we want to efficiently vary both the speed and direction of
a brushed DC motor22.
To illustrate this, we can use the same circuit as in the last example, with the potentiometer controlling the
motor in the same way as in example 6: when the pot is rotated fully clockwise, the motor will spin at full

22
and of course other types of motor

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 35


© Gooligum Electronics 2013 www.gooligum.com.au

speed in the “forward” direction. When the pot is at the other extreme, the motor will spin at full speed in
the “reverse” direction. The motor speed will vary smoothly between these extremes as the pot is adjusted,
coming to a stop when the pot is half-way.

To implement this, we can modify the last example.

As we’ve seen, setting the P1M bits in the CCP1CON register to ‘01’ select full-bridge output forward
PWM mode, while setting the P1M bits to ‘11’ selects full-bridge output reverse.
Thus, setting P1M<0> selects full-bridge output mode, regardless of the direction.
The P1M<1> bit then controls the direction: clearing it selects forward mode, and setting it selects reverse.

Our motor direction will be controlled by the AN0 input, so we’ll need to select the direction in the main
loop, after reading AN0.
This means that, when we configure the ECCP module in the initialisation code, we don’t care (at this stage)
what the motor direction is; we only need to ensure that P1M<0> is set:
// configure ECCP module
CCP1CONbits.P1M0 = 1; // select full-bridge output mode
// -> PIA, P1B, P1C, P1D are PWM outputs
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1110; // select PWM mode: P1A, P1C active-low
// P1B, P1D active-high
// -> full-bridge output mode, PWM outputs:
// P1A active-low
// P1B active-high
// P1C active-low
// P1D active-high

Note that, in the comments, we’re no longer describing specific PWM outputs as “modulated” or “active”, as
these roles will change depending on whether forward or reverse mode is selected.

We want the motor to spin forward if the pot is turned more than half-way clockwise. On the Gooligum
training board, this corresponds to a voltage on AN0 of more than VDD/2.
If we continue to configure the ADC with the most significant eight bits of the result in ADRESH, the value
in ADRESH will vary from 0 with the pot fully counter clockwise to 255 when is fully clockwise.
Mid-way, the ADC result will be 127 or 128 (we have to use whole numbers here…).
So we want:
Motor full speed reverse: ADRESH = 0
Motor stopped: ADRESH = 127 or 128
Motor full speed forward: ADRESH = 255

In mid-range assembler lesson 18, we noted that this meant that we could use the most significant bit of the
ADC result (ADRESH<7>) to indicate the motor direction:
If ADRESH<7> = 1, ADRESH ≥ 128 and the motor is either spinning forward or stopped.
Otherwise, it is either spinning in reverse or stopped.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 36


© Gooligum Electronics 2013 www.gooligum.com.au

However, in C it is more natural to compare the ADC result with a threshold value, instead of testing a single
bit, so we will use the construct:
// set motor direction and speed
if (ADRESH >= 128) // value of ADRESH determines motor direction
{
//*** forward mode

}
else
{
//*** reverse mode

It would seem tempting to ‘#define’ the threshold as a symbol (constant) at the start of the code, and relate
all the direction decisions and duty cycle calculations to this constant, so that you can easily change the
threshold later (you might decide that only the lower 1/3 of the potentiometer’s range should be for reverse,
and the upper 2/3 for forward mode), without having to modify any of this code. As we’ve seen before, that
type of abstraction can be a very good idea, making the program more maintainable, but we’ve also seen that
C compilers (including XC8) generate more efficient code when multiplying or dividing by powers of two.
Making the threshold arbitrary could lead (inadvertently) to some very inefficient calculations, potentially
using a lot of memory to implement them. On the other hand, making this code more general and flexible is
challenge that you may wish to take on!

In forward mode, we want ADRESH = 128 to correspond to stopped (PWM duty cycle = 0%) and
ADRESH = 255 to correspond to full speed (PWM duty cycle = 100%).
So, ideally, we would want:
duty cycle = (ADRESH – 128) ÷ 127
We previously set the duty cycle by copying ADRESH to CCPR1L, and with the DC1B bits in the
CCP1CON register clear, the PWM duty cycle is equal to CCPR1L ÷ 256.
Again, ideally, this means that we should calculate:
CCPR1L = (ADRESH – 128) × 256 ÷ 127
= (ADRESH – 128) × 2.01575

That’s an unnecessarily complex calculation for the compiler to implement, even if performed using fixed-
point arithmetic instead of floating point, when the value we are multiplying by is so close to 2.
We can get very close to the “correct” duty cycle with the much simpler:
// set new PWM duty cycle
CCPR1L = (ADRESH-128)*2; // PWM duty cycle =
// (high byte of ADC result - 128) / 128

If ADRESH = 128, (ADRESH – 128) × 2 = 0 and CCPR1L = 0 → PWM duty cycle = 0%.
If ADRESH = 255, (ADRESH – 128) × 2 = 254 and CCPR1L = 254 → PWM duty cycle = 99.2%.
That’s not quite 100%, but it’s close enough for the purposes of this example, so we’ll use this simpler duty
cycle calculation.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 37


© Gooligum Electronics 2013 www.gooligum.com.au

Having set the new duty cycle, we can simply clear the P1M<1> bit to select full-bridge forward mode:
// select forward direction
CCP1CONbits.P1M1 = 0; // select full-bridge output forward mode
// -> P1D modulated (active-high)
// P1A active (low)
// P1B inactive (low)
// P1C inactive (high)

When the direction control bit (P1M<1>) is changed, the ECCP module will wait until the end of the current
PWM period to effect the change.
The new direction and duty cycle will then be used for the following PWM period.

In reverse mode, we want ADRESH = 127 to correspond to stopped (PWM duty cycle = 0%) and ADRESH
= 0 to correspond to full speed (PWM duty cycle = 100%).
So ideally:
duty cycle = (127 – ADRESH) ÷ 127
and:
CCPR1L = (127 – ADRESH) × 256 ÷ 127
= (127 – ADRESH) × 2.01575

Again, that’s an unnecessarily complex calculation, when the value we are multiplying by is so close to 2.
We can get very close to the “correct” duty cycle with the much simpler:
// set new PWM duty cycle
CCPR1L = (127-ADRESH)*2; // PWM duty cycle =
// (127 - high byte of ADC result) / 128

If ADRESH = 127, (127 – ADRESH) × 2 = 0 and CCPR1L = 0 → PWM duty cycle = 0%.
If ADRESH = 0, (127 – ADRESH) × 2 = 254 and CCPR1L = 254 → PWM duty cycle = 99.2%.
Again, that’s not quite 100%, but it’s close enough for this example.

We can now go ahead and set the P1M<1> bit to select full-bridge reverse mode:
// select reverse direction
CCP1CONbits.P1M1 = 1; // select full-bridge output reverse mode
// -> P1B modulated (active-high)
// P1C active (low)
// P1A inactive (low)
// P1D inactive (high)

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 38


© Gooligum Electronics 2013 www.gooligum.com.au

Complete program
Here is the full source code, showing how all these pieces fit together:
/************************************************************************
* *
* Description: Lesson 12, example 8 *
* *
* Demonstrates variable speed bidirectional brushed DC motor control, *
* using full-bridge PWM mode *
* *
* Outputs PWM signals (~244 Hz) on P1A-D in full-bridge mode *
* with direction snd duty cycle derived from an analog input *
* *
* If analog input is full-range potentiometer, motor will be: *
* full speed forward when pot fully at one extreme, *
* full speed reverse when pot fully at other extreme, *
* stopped when pot centred *
* *
* Implemented through this logic: *
* If 8-bit ADC result >= 128, *
* Select forward mode *
* PWM duty cycle = (result-128)/128 *
* Else *
* Select reverse mode *
* PWM duty cycle = (127-result)/128 *
* *
*************************************************************************
* *
* Pin assignments: *
* P1A = active-low PWM output *
* P1B = active-high PWM output *
* P1C = active-low PWM output *
* P1D = active-high PWM output *
* AN0 = analog input (e.g. pot or LDR) *
* *
************************************************************************/

#include <xc.h>
#include <stdint.h>

/***** CONFIGURATION *****/


// ext reset, no code or data protect, no brownout detect
#pragma config MCLRE = ON, CP = OFF, CPD = OFF, BOREN = OFF
// no watchdog, power-up timer enabled, int 4 MHz oscillator with I/O
#pragma config WDTE = OFF, PWRTE = ON, FOSC = INTOSCIO
// no failsafe clock monitor, two-speed start-up disabled
#pragma config FCMEN = OFF, IESO = OFF

/***** MAIN PROGRAM *****/


void main()
{
/*** Initialisation ***/

// configure ports
TRISC = 0b000011; // configure PORTC as all inputs
// except RC2-5 (P1A-D outputs)
ANSEL = 1<<0; // make AN0 (only) analog

// configure ADC

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 39


© Gooligum Electronics 2013 www.gooligum.com.au

ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2.0 us (with Fosc = 4 MHz)


ADCON0bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON0bits.VCFG = 0; // voltage reference is Vdd
ADCON0bits.CHS = 0b000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// configure Timer2
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
PR2 = 255; // period = 256 x 16 us = 4096 us
// -> PWM frequency = 244 Hz
// configure ECCP module
CCP1CONbits.P1M0 = 1; // select full-bridge output mode
// -> PIA, P1B, P1C, P1D are PWM outputs
CCP1CONbits.DCB = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1110; // select PWM mode: P1A, P1C active-low
// P1B, P1D active-high
// -> full-bridge output mode, PWM outputs:
// P1A active-low
// P1B active-high
// P1C active-low
// P1D active-high

/*** Main loop ***/


for (;;)
{
// sample analog input
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.nDONE) // wait until done
;

// set motor direction and speed


if (ADRESH >= 128) // value of ADRESH determines motor direction
{
//*** forward mode

// set new PWM duty cycle


CCPR1L = (ADRESH-128)*2; // PWM duty cycle
// = (high byte of ADC result - 128) / 128

// select forward direction


CCP1CONbits.P1M1 = 0; // select full-bridge output forward mode
// -> P1D modulated (active-high)
// P1A active (low)
// P1B inactive (low)
// P1C inactive (high)
}
else
{
//*** reverse mode

// set new PWM duty cycle


CCPR1L = (127-ADRESH)*2; // PWM duty cycle
// = (127 - high byte of ADC result) / 128

// select reverse direction


CCP1CONbits.P1M1 = 1; // select full-bridge output reverse mode
// -> P1B modulated (active-high)

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 40


© Gooligum Electronics 2013 www.gooligum.com.au

// P1C active (low)


// P1A inactive (low)
// P1D inactive (high)
}
}
}

Conclusion
This has been another very long lesson, especially for one that explores only a single feature of the ECCP
module: pulse-width modulation (PWM).
We’ve seen that PWM can be used to generate an output that, when averaged (whether by a simple RC filter,
the characteristics of a load such as a motor, or even the persistence of human vision), behaves as though it is
a finely-variable “analog” output. We also saw that such an output can be used to control the power
delivered to a load – such as the brightness of an LED or the speed of a motor.
And we saw that the ECCP module’s half-bridge and full-bridge PWM output modes are ideally suited,
when driving appropriate circuitry, to controlling both the speed and direction of brushless DC motors.

We also saw, especially in the last example, that using a C compiler such as XC8, instead of programming in
assembly language, makes it very straightforward to perform the arithmetic sometimes necessary to calculate
duty cycle values.

That brings us nearly to the end of the mid-range PIC architecture and assembly language lessons. We have
covered almost every significant feature of the PIC16F684 – except one: EEPROM data memory.
In our final lesson we’ll see how the 16F684’s EEPROM memory can be used to preserve data when the PIC
is powered off.

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 41


© Gooligum Electronics 2013 www.gooligum.com.au

Appendix A – Additional parts required for motor control examples


The following parts are additional to those included with the Gooligum mid-range PIC training board, and
they (or their equivalents) are required to complete the motor control examples in this lesson.
They can be ordered as a “motor control” kit of components through the mid-range training board page, on
www.gooligum.com.au.

Qty Value Description

2 PSMN022-30PL N-channel logic level MOSFETs


or similar

2 NDP6020P P-channel logic level MOSFETs


or similar

1 SN754410 Quad half-H driver with internal clamp diodes


or L293D

1 MDN3BL3CSAS 2 - 5 V brushed DC motor with wire leads


or similar

1 1N4004 Silicon rectifier diode


or similar

4 220 Ω 5% 0.25W resistors

2 10 kΩ 5% 0.25W resistors

Mid-Range PIC C, Lesson 12: CCP, part 2 – Pulse-Width Modulation Page 42

You might also like