You are on page 1of 47

© Gooligum Electronics 2015 www.gooligum.com.

au

Introduction to PIC Programming


Programming Enhanced Mid-Range PICs in C

by David Meiklejohn, Gooligum Electronics

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

The previous lesson introduced the Capture/Compare/PWM (CCP) and Enhanced CCP (ECCP) modules
and demonstrated their capture and compare modes, which can be used (in capture mode) to accurately time
external signals or (in compare mode) to automatically schedule events such as toggling a pin
We introduced digital output way back in lesson 1, and we’ve been flashing LEDs ever since. And we saw
in lesson 9 that some PICs provide a simple digital-to-analog converter which can be used to output an
analog voltage with limited resolution on a single pin.
What if we wanted greater resolution, so that we could smoothly adjust the output voltage? What if we
wanted to adjust the average power delivered to a non-linear load, such as an LED or motor? For example,
we may not wish to simply flash an LED – we might want to dim it. Or, instead of only being able to turn a
motor on or off, we may need to adjust its speed – and perhaps even change its direction.
This lesson introduces the CCP and ECCP modules’ pulse-width modulation (PWM) modes, which allow us
to control the average voltage (and power) supplied to a load. This is done by rapidly toggling one or more
outputs with a variable duty cycle, which can be adjusted smoothly, with up to 10-bit resolution. As the duty
cycle increases, so too does the average delivered power.

In summary, this lesson introduces:


 the CCP module’s standard single-output pulse-width modulation mode
 the ECCP module’s enhanced pulse-width modulation modes, including
o single output with PWM steering
o half-bridge output
o full-bridge output (forward and reverse)

The examples, implemented using XC8, 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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 1
© Gooligum Electronics 2015 www.gooligum.com.au

PIC16F1824 CCP Modules Standard Pulse-Width Modulation (PWM) Mode


As we saw in the previous lesson, the PIC16F1824 includes two ECCP (ECCP1 and ECCP2) and two CCP
modules (CCP3 and CCP4). The capture and compare functions are identical in all four modules, but their
pulse-width modulation functionality differs.
The two standard CCP modules only provide standard single-output PWM, which, because it’s simpler, we’ll
look at first. The two enhanced CCP modules also provide additional (“enhanced”) PWM features, which
we’ll look at later.

Each CCP module is controlled by bitfields in its CCPxCON register (where ‘x’ is ‘3’ or ‘4’):

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


CCPxCON - - DCxB<1:0> CCPxM<3:0>

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


Clearing these mode select bits turns off and resets the CCP module.
Setting the CCPxM<3:2> bits to ‘11’ selects PWM mode1.

Each CCP module can, in standard PWM mode, output a single PWM signal on its CCPx pin2.
The pin used for each PWM output must be configured as an output by clearing its associated TRIS bits – for
example, on the 16F1824 we need to clear TRISA<2> to enable the CCP3 (same pin as RA2) output.
Each PWM output may drive a load directly, or activate a switch (such as a MOSFET) or some type of
driver. Standard PWM is “active high” – the load is considered to be “on” when the PWM output is high.

PWM period and duty cycle


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

Period

Pulse Width

TMRn = PRn+1
TMRn = CCPRxL
TMRn = 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).

1
the CCPxM<1:0> bits are unused in standard PWM mode
2
CCP3 is shared with RA2 and CCP4 is shared with RC1 on the PIC16F1824

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 2
© Gooligum Electronics 2015 www.gooligum.com.au

The timing of these pulses – their period and width – is driven by one of the ‘Timer2’ modules, of which, as
we saw in lesson 13, the PIC16F1824 has three: Timer2, Timer4 and Timer6 (TMR2, TMR4 and TMR6).
Each of the four (E)CCP modules can be associated with any of the three ‘Timer2’ modules.
A single ‘Timer2’ module can provide the timebase for multiple CCP modules, in which case the PWM
signals generated by those modules would have the same frequency. For example, TMR2 could be
providing the timebase for both CCP3 and CCP4, while TMR4 drives ECCP1 and TMR6 drives ECCP2 at
different frequencies.
The PWM timer for each (E)CCP module is selected by the CCPTMRS0 register:

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


CCPTMRS0 C4TSEL<1:0> C3TSEL<1:0> C2TSEL<1:0> C1TSEL<1:0>

The CxTSEL bits select the timer to be used as a CxTSEL<1:0> PWM timer
timebase for the corresponding (E)CCP module’s
PWM mode, as shown in the table on the right3. 00 Timer2 (TMR2)

For example, if C3TSEL = ‘01’, Timer4 will be 01 Timer4 (TMR4)


assigned to CCP3. 10 Timer6 (TMR6)

As explained in more detail in lesson 13, these timers can only be driven by the instruction clock (period
TCY), which is four times slower than the processor clock (period TOSC, frequency FOSC).
Each timer can optionally be used with a prescaler, with a ratio of 1:4, 1:16 or 1:64.
Each TMRn register increments until it matches the value stored in the corresponding 8-bit period register,
PRn (where ‘n’ is ‘2’, ‘4’ or ‘6’) and then resets to 0 on the next increment cycle.
This means that TMRn has a period equal to PRn+1.
The PWM period is the same as timer period.
Hence:
PWM period = (PRn+1) × TCY × (timer prescale ratio)
PWM frequency = FOSC / (4 × (PRn+1) × (timer prescale ratio))

For example, if we use Timer2 as our PWM timebase with a 4 MHz processor clock and configure it 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

3
Note that, because the PIC16F1824 only has three ‘Timer2’ modules, there is no ‘11’ option.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 3
© Gooligum Electronics 2015 www.gooligum.com.au

As illustrated above, the PWM pulse begins when TMRn resets to 0.


The PWM output goes high at the start of the pulse.
It then remains active until TMRn matches the value stored in the CCP module’s CCPRxL register
(where ‘n’ is ‘2, ‘4’ or ‘6’ and ‘x’ is ‘3’ or ‘4’).
The PWM output then goes low, and remains low until TMRn reaches PRn and resets on the next increment
cycle, starting the next pulse.
Thus, the pulse width, and hence the duty cycle4, is specified by the value in CCPRxL.

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


TMRn and CCPRxL are 8-bit registers. Thus, if the duty cycle was only defined by CCPRxL, the PWM
resolution could be no more than eight bits – meaning that no more than 256 different duty cycles would be
available.
Some applications need finer output control than that, so Microchip implemented a scheme to extend the
PWM resolution to ten bits, providing for up to 1024 discrete duty cycles.
This is done by adding two less-significant bits to TMRn. These are derived from either the processor clock
(FOSC) or the timer’s prescaler. Either way, they 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 DCxB bits in the CCPxCON register.
This means that the pulse width is actually specified by a 10-bit value consisting of the 8-bit value in
CCPRxL concatenated with the two DCxB bits5.

Hence, the pulse width is given by:


PWM pulse width = (CCPRxL:DCxB) × TOSC × (timer prescale ratio)

The duty cycle can be calculated as:


PWM duty cycle = (CCPRxL:DCxB) / (4 × (PRn+1))

For example, if we configure the processor clock and Timer2 as above, assign it as the PWM timebase for
CCP3, and load 100 (decimal) into CCPR3L and clear the DC3B bits:
CCPR3L:DC3B = 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.6

4
duty cycle = pulse width / period
5
this is equivalent to left-shifting the value in CCPRxL two times (multiplying it by four), then adding the DCxB bits
6
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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 4
© Gooligum Electronics 2015 www.gooligum.com.au

Note that when PRn = 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 PRn value less than 255.

Finally, it’s worth noting that the CCPRxL register and DCxB bits are not used directly in the comparison
with TMRn (and its two hidden less-significant bits). Instead, they are copied to the CCPRxH 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 CCPRxL register and DCxB 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…

Example 1: Analog output


Although the PWM 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 from CCP4, so that
we can see clearly what’s going on.

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 below, is often adequate. Note however that the output would usually be buffered with an
op-amp, to avoid loading the filter and affecting its operation.
The high-frequency component of the
PWM signal on pin CCP4, Vpwm, is
attenuated, giving a much smaller ripple
voltage in the output, Vout:

For a simple RC filter, it can be shown


that:

where Fpwm is the PWM frequency.


To calculate the R and C values needed
to produce a given attenuation, you can
use:

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 5
© Gooligum Electronics 2015 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.

On one hand, 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 the analog signal that you’re generating (or,
equivalently, allowing the load to respond quickly to level changes), 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 below 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:


K = 251 (an attenuation of 48 dB)
Vout = 0.004 × Vpwm
f3dB = 15.9 Hz
I.e. the AC ripple in the output is only 0.4% of the AC component of the original PWM signal, which is very
good, 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 CCP4 (pin 12, ‘RC1’ 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.
Ideally you’ll want to use an oscilloscope to observe the output, but if you don’t have one you should at least
be able to use a digital multimeter to measure the output voltage, to see for yourself that the expected
average voltage is being produced, for each PWM duty cycle.

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

We’ll configure the PIC to use the internal RC oscillator, as usual:


/***** CONFIGURATION *****/
// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = OFF

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 6
© Gooligum Electronics 2015 www.gooligum.com.au

And configure it to generate a 4 MHz processor clock:


// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

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

The CCP4 pin (RC1) must be configured as an output:


TRISC = ~(1<<1); // configure PORTC as all inputs
// except RC1 (CCP4 output)

We’ll use Timer2 as the CCP4 PWM timebase, but before we can use it, we have to select it:
CCPTMRSbits.C4TSEL = 0b00; // use Timer2 with CCP4

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 CCPRxL:DCxB<1:0>, and that this 10-bit value is the number of
“quarter timer 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 CCPR4L:DC4B<1:0> with 500, so we have:
CCPR4L = 125 (= 500÷4)
DC4B =0 (= ‘00’ binary)

Perhaps an easier way to look at this is to say that CCPR4L is equal to the whole part of the pulse width
measured in “TMR2 increments”, and the DC4B bits hold any fractional remainder (0 in this case).
Either way, to configure the CCP4 module and specify the pulse width, we have:
// configure CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 125; // CCPR4L:DC4B<1:0> = 500 -> pulse width = 125 us
// -> PWM duty cycle = 50%

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 7
© Gooligum Electronics 2015 www.gooligum.com.au

With Timer2 running and the CCP4 module configured like this, a 4 kHz signal with 50% duty cycle will
appear on the CCP4 pin, as shown below:
The yellow
trace is the
PWM output
on CCP4, 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:
main_loop
; do nothing
goto main_loop

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

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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 8
© Gooligum Electronics 2015 www.gooligum.com.au

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


// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = OFF

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


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

// configure ports
TRISC = ~(1<<1); // configure PORTC as all inputs
// except RC1 (CCP4 output)

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

// Setup PWM
// select PWM timer
CCPTMRSbits.C4TSEL = 0b00; // use Timer2 with CCP4
// 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 CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 125; // CCPR4L:DC4B<1:0> = 500 -> pulse 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 CCPR4L register and the DC4B bits.

For example, for a 25% duty cycle, the pulse width will be 25% × 250 µs = 62.5 µs.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 9
© Gooligum Electronics 2015 www.gooligum.com.au

CCPR4L holds the whole number of TMR2 increments in the pulse width (62), and the DC4B bits represent
the fractional remainder (0.5) as a number of “quarter increments” (0.5 × 4 = 2 = ‘10’ binary).
The CCP configuration becomes:
// configure CCP4
CCP4CONbits.DC4B = 0b10; // LSBs of PWM duty cycle = 10
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 62; // CCPR4L:DC4B<1:0> = 250 -> pulse 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 CCPR4L = 187.
The 0.5 µs remainder is 2 × quarter increments (0.25 µs each), so DC4B<1:0> = 2 (= ‘10’ binary).
The CCP configuration is then:
// configure CCP4
CCP4CONbits.DC4B = 0b10; // LSBs of PWM duty cycle = 10
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 187; // CCPR4L:DC4B<1:0> = 750 -> pulse width = 187.5 us
// -> PWM duty cycle = 75%

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 10
© Gooligum Electronics 2015 www.gooligum.com.au

What about a 0% duty cycle, where there is no pulse at all (meaning that the load will be fully “off” or
“inactive”)?
That’s easy – there are no TMR2 increments, and no remainder, so CCPR4L = 0, DC4B<1:0> = 0 and we
have:
// configure CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 0; // CCPR4L:DC4B<1:0> = 0 -> pulse width = 0 us
// -> PWM duty cycle = 0%

Of course, if you really wanted nothing more than a “0% duty cycle”, you’d simply configure RC1 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 to7 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 CCPR4L = 250.
There is no fractional remainder, so DC4B<1:0> = 0 (= ‘00’ binary).
The CCP configuration is then:
// configure CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 250; // CCPR4L:DC4B<1:0> = 1000 -> pulse width = 250 us
// -> PWM duty cycle = 100%

Again, if your program did nothing more than output a “100% active” signal, you’d simply configure RC1 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 best
you can do is a duty cycle of 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!

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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 11
© Gooligum Electronics 2015 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:
// configure Timer2
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 – but 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 CCP configuration is:
// configure CCP4
CCP4CONbits.DC4B = 0b10; // LSBs of PWM duty cycle = 10
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 62; // CCPR4L:DC4B<1:0> = 250 -> pulse width = 250 us
// -> PWM duty cycle = 25%

This is exactly the same (except for the comments) 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.
The Timer2 setup is then:
// configure Timer2
T2CONbits.T2CKPS = 0b10; // 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.
Once again, for a 25% duty cycle, we have:
// configure CCP4
CCP4CONbits.DC4B = 0b10; // LSBs of PWM duty cycle = 10
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4
CCPR4L = 62; // CCPR4L:DC4B<1:0> = 250 -> pulse width = 1000 us
// -> PWM duty cycle = 25%

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 12
© Gooligum Electronics 2015 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 CCP4, and
the green trace
is the filtered
output.
The AC ripple
in the output is
now very
obvious, and
we’d have to
conclude 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: 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, because persistence of vision averages the light from the LED, there is no need for an RC filter
circuit, as we’d used in example 1.
.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 13
© Gooligum Electronics 2015 www.gooligum.com.au

If we’re only dimming a single LED8, we can


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

If you are using the Gooligum training board,


you can implement this circuit by placing a shunt
across pins 1 and 2 (‘POT’) of JP24, connecting
the 10 kΩ pot (RP2) to AN09, and in JP17 to
enable the LED on RC1 (CCP4).

Recall that the duty cycle is given by:


PWM duty cycle = (CCPRxL:DCxB) / (4 × (PRn+1))
and that, if we clear the DCxB bits, we have simply:
PWM duty cycle = CCPRxL / (PRn+1)

For the highest PWM resolution, where the duty cycle can be adjusted in the smallest-possible increments,
we should make the value of PRn as high as possible, which, because it’s an 8-bit register, is 255.
With PRn = 255, and the DCxB bits clear, we have:
PWM duty cycle = CCPRxL / 256

Each CCPRxL register holds an 8-bit value, 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 DCxB bits:
PWM duty cycle = (CCPRxL:DCxB) / 1024

Keep in mind that, if you increase the PWM frequency by making the value in PRn smaller, you will
decrease the PWM duty cycle resolution.

In this example (just to be different) we’ll use Timer6 as the CCP4 PWM timebase:
// select PWM timer
CCPTMRSbits.C4TSEL = 0b10; // use Timer6 with CCP4

8
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.
9
or, if you place the shunt across pins 2 and 3 (‘LDR1’) of JP24, the LED will be controlled by photocell PH1

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 14
© Gooligum Electronics 2015 www.gooligum.com.au

With a 4 MHz processor clock, and no Timer6 prescaler, TMR6 will increment every 1 µs:
// configure Timer6
T6CONbits.T6CKPS = 0b00; // prescale = 1
T6CONbits.TMR6ON = 1; // enable timer
// -> TMR6 increments every 1 us

We want to demonstrate the full range of duty cycles, so we’ll make PR6 = 25510:
PR6 = 255; // period = 256 x 1 us = 256 us
// -> PWM frequency = 3906 Hz

With PR6 = 255 the TMR6 period is then 256 × 1 µs = 256 µs, and our PWM frequency is 3906 Hz:
This is more than fast enough that the LED won’t perceptibly flicker.

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 DC4B bits, and leave them cleared:
// configure CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4

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 (see lesson 11) with:
// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

Whenever we sample AN0, the 8-bit ADC result will now appear in ADRESH, which, to update the duty
cycle, we can simply copy to CCPR4L, within the main loop:
// set new PWM duty cycle
CCPR4L = ADRESH; // 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 DC4B bits in CCP4CON.
But for this application there’s really no need to go to that extra effort11.

10
At power-on, the value in PR6 is 255 by default, so strictly-speaking there is no need to load 255 into PR6 like this.
But it’s good practice to explicitly do so, in case you want to change the PWM frequency later.
11
you might want to try it anyway, as an exercise...

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 15
© Gooligum Electronics 2015 www.gooligum.com.au

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

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

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


// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = OFF

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


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

// configure ports
TRISC = ~(1<<1); // configure PORTC as all inputs
// except RC1 (CCP4 output)
ANSELA = 1<<0; // select analog mode for RA0
// -> RA0/AN0 is an analog input

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 16
© Gooligum Electronics 2015 www.gooligum.com.au

// Setup PWM
// select PWM timer
CCPTMRSbits.C4TSEL = 0b10; // use Timer6 with CCP4
// configure Timer6
T6CONbits.T6CKPS = 0b00; // prescale = 1
T6CONbits.TMR6ON = 1; // enable timer
// -> TMR6 increments every 1 us
PR6 = 255; // period = 256 x 1 us = 256 us
// -> PWM frequency = 3906 Hz
// configure CCP4
CCP4CONbits.DC4B = 0b00; // LSBs of PWM duty cycle = 00
CCP4CONbits.CCP4M = 0b1100; // select PWM mode
// -> single PWM output on CCP4

/*** Main loop ***/


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

// set new PWM duty cycle


CCPR4L = ADRESH; // 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” code so that, if the ADC result is below some
threshold, the duty cycle is set to zero.

Example 3: Simple unidirectional brushed DC motor control


One of the most common uses of PWM is to control electric motors. Although PWM can be (and often 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, 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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 17
© Gooligum Electronics 2015 www.gooligum.com.au

It would be rare to find a motor that can operate with less than the 25 mA that one of the PIC16F1824’s pins
can supply. Indeed, even the tiny motor supplied in the Gooligum training board motor control kit can draw
more than 100 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, to indirectly control the supply of current to
the motor.

A straightforward approach is to use a motor driver chip, such as the L293D or the newer, pin-compatible
SN754410, as shown in the circuit below:

We’ll use the CCP3 output, simply to demonstrate that it’s used and setup exactly the same way as CCP4;
the CCP3 and CCP4 modules are identical.
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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 18
© Gooligum Electronics 2015 www.gooligum.com.au

If you do use a single 5 V supply for the digital and output circuitry (as shown in the photo 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 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 3 to power the logic circuits, including the PIC.

You can build this circuit with the Gooligum training board, using the L293D (or SN754410) IC and motor
supplied in the motor control kit – connecting them to signals on the 16-pin header: CCP3 on pin 13
(‘GP/RA/RB2’), +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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 19
© Gooligum Electronics 2015 www.gooligum.com.au

An alternative approach is to use an N-channel logic level MOSFET switch, as in the circuit below.
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 CCP3
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
Gooligum training board
are the same as before, with
the 220 Ω and 10 kΩ
resistors connecting to pin
13 (‘GP/RA/RB2’), and +5
V and ground to pins 15
(‘+V’) and 16 (‘GND’) on
the 16-pin header.
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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 20
© Gooligum Electronics 2015 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 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 an L293D or SN754410 driver or the N-channel MOSFET, the method of PWM control is
exactly the same: a “high” on the CCP3 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.

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 timer prescaler.
And, just to show again that it doesn’t matter which ‘Timer2’ module is used (they are all identical), we’ll
use Timer4 as the CCP3 PWM timebase:
// select PWM timer
CCPTMRSbits.C3TSEL = 0b01; // use Timer4 with CCP4

Selecting the 1:16 prescaler increases the TMR4 period by 16 times, to 4096 µs:
// configure Timer4
T4CONbits.T4CKPS = 0b10; // prescale = 16
T4CONbits.TMR4ON = 1; // enable timer
// -> TMR4 increments every 16 us
PR4 = 255; // period = 256 x 16 us = 4096 us
// -> PWM frequency = 244 Hz

CCP3 is then configured the same way as we configured CCP4 in the previous example:
// configure CCP3
CCP3CONbits.DC3B = 0b00; // LSBs of PWM duty cycle = 00
CCP3CONbits.CCP3M = 0b1100; // select PWM mode
// -> single PWM output on CCP3

Finally, since we’re now using CCP3 instead of CCP4, we’ll now copy the 8-bit ADC result to CCPR3L in
the main loop:
// set new PWM duty cycle
CCPR3L = ADRESH; // duty cycle = high byte of ADC result / 256

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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 21
© Gooligum Electronics 2015 www.gooligum.com.au

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

PIC16F1824 ECCP Modules Enhanced PWM Modes


As mentioned earlier, the PIC16F1824 includes two enhanced CCP (ECCP) modules: ECCP1 and ECCP2.
In addition to all the features of the standard CCP modules (CCP3 and CCP4), the ECCP modules provide a
number of “enhanced” PWM modes.
These enhanced PWM modes generate the PWM signal in the same way as for standard PWM – its timing is
driven by one of the ‘Timer2’ timers interacting with a CCPRxL register and DCxB bits, exactly as before.
The enhancements relate to how the PWM signal is output.
Enhanced CCP modules are able to output the PWM signal in various ways on up to four pins, referred to as
PxA, PxB, PxC and PxD.
On the PIC16F1824, the ECCP1 module can use up to four PWM output pins: P1A, P1B, P1C and P1D
(shared by default with RC5, RC4, RC3 and RC2 respectively12).
The PIC16F1824’s ECCP2 module can use up to two PWM outputs: P2A and P2B, shared by default with
RC3 and RC2 respectively13.

Enhanced PWM output modes


Four enhanced PWM output modes are available:
 Single output mode is the simplest, where a single PWM signal is output on the PxA pin14.
 In half-bridge output mode, the PWM signal is output on PxA, and the inverse of the PWM signal is
output on PxB. 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, which can be driven in “forward” or “reverse” direction. In
these modes, all four PWM outputs, PxA – PxD, are used.
Not that, since the full-bridge modes require four PWM outputs, they are only available on the
ECCP1 module on the PIC16F1824, because the ECCP2 module has only two output pins (P2A and
P2B) and can therefore only be used in single and half-bridge output modes.

Each ECCP module is controlled by a CCPxCON register (where ‘x’ is ‘1’ or ‘2’):

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


CCPxCON PxM<1:0> DCxB<1:0> CCPxM<3:0>

This is very similar to the CCP control registers we’ve seen before, but the PxM<1:0> bits are new.

12
P1C and P1D can alternately be assigned to RC1 and RC0 (respectively), using the APFCON1 alternate pin
function control register – see the data sheet for details
13
P2A and P2B can alternately be assigned to RA5 and RA4, via the APFCON1 register (see the data sheet)
14
PWM steering can be used to output the PWM signal on any combination of the PxA – PxD pins, as we’ll see later

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 22
© Gooligum Electronics 2015 www.gooligum.com.au

They are used to select the PWM output mode, as shown in the table below:

PxM<1:0> PWM output mode PxA PxB PxC PxD


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.

As in standard PWM mode, the pin(s) used in each enhanced PWM output mode must be configured as
outputs by clearing their associated TRIS bits – for example, on the 16F1824 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.

We saw earlier that, in standard PWM mode, the PWM signal is considered to be “active high” – the load is
“on” or “active” when the PWM output is high.
However, it is sometimes preferable (or necessary) to activate the load or switch via a low PWM output,
which we would refer to as active-low.
Unlike standard PWM, the enhanced PWM modes can (optionally) generate active-low signals.
That is why the PWM outputs are described as “active” or “inactive” above, instead of high or low; whether
a high or low output activates your load will depend on your circuit.
The CCPxM<1:0> bits are used to configure the enhanced PWM outputs as being active-high or active-low,
as shown in the table below:

CCPxM<3:0> PxA PxB PxC PxD


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: PxA and PxC always have the same configuration, as do
PxB and PxD. 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.

Single-output enhanced PWM


In single-output mode the modulated signal is output, by default, on the PxA pin, although, as we’ll see later,
PWM steering can be used to direct the PWM signal to any of the PxA – PxD pins.
Single-output enhanced PWM is essentially the same as standard PWM, except that we can now use the
CCPxM<1:0> bits to select the polarity (active-high or -low) of the PWM signal.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 23
© Gooligum Electronics 2015 www.gooligum.com.au

Otherwise, single-output enhanced PWM is setup and used in the same way as the standard PWM we’re
familiar with, as we’ll see in the following example.

Example 4: 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 complex15 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 P1A.
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 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
simple beeps it’s quite adequate.

We’ll drive the piezo with a variable frequency


controlled by the potentiometer connected to AN0.

If you have the Gooligum training board, you can implement this circuit by placing a shunt across pins 1 and
2 (‘POT’) of JP2416, 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.

In this example we’re using the ECCP1 module, and we’ll select Timer2 as its PWM timebase:
// select PWM timer
CCPTMRSbits.C1TSEL = 0b00; // use Timer2 with ECCP1

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

15
a topic for a future tutorial?
16
or place the shunt across pins 2 and 3 (‘LDR1’) of JP24 to make a light-controlled musical instrument!

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 24
© Gooligum Electronics 2015 www.gooligum.com.au

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


// configure Timer2
T2CONbits.T2OUTPS = 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 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:
ANSELA = 1<<0; // select analog mode for RA0
// -> RA0/AN0 is an analog input

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:
// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

The ECCP1 module is configured in much the same way as the CCP modules in the earlier examples, except
that we now need to explicitly select single-output mode with P1M<1:0> = ‘00’ and an active-high output
with CCP1M<1:0> = ‘00’:
// configure ECCP1
CCP1CONbits.P1M = 0b00; // select single output mode
// -> P1A active
CCP1CONbits.DC1B = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single active-high PWM output on P1A

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

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 13) that if the Timer2 postscale ratio is set to 1:1, the
TMR2IF interrupt flag will be set whenever Timer2 overflows.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 25
© Gooligum Electronics 2015 www.gooligum.com.au

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
;

That’s all fairly straightforward. The only real complication is that, if we want 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!

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%

Complete program
This is how all these pieces fit together:
/************************************************************************
* Description: Lesson 15, example 4 *
* *
* Demonstrates varying single-output PWM frequency (fixed duty cycle) *
* *
* Sounds piezo on P1A at 244 - 62500 Hz, *
* with frequency derived from an analog input *

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 26
© Gooligum Electronics 2015 www.gooligum.com.au

*************************************************************************
* Pin assignments: *
* P1A = PWM output (e.g. piezo speaker) *
* AN0 = analog input (e.g. pot or LDR) *
* *
************************************************************************/

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

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


// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = 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 (P1A output)
ANSELA = 1<<0; // select analog mode for RA0
// -> RA0/AN0 is an analog input

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// select PWM timer
CCPTMRSbits.C1TSEL = 0b00; // use Timer2 with ECCP1
// configure Timer2
T2CONbits.T2OUTPS = 0; // postscale = 1:1
T2CONbits.T2CKPS1 = 1; // prescale = 16
T2CONbits.TMR2ON = 1; // enable timer
// -> TMR2 increments every 16 us
// configure ECCP1
CCP1CONbits.P1M = 0b00; // select single output mode
// -> P1A active

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 27
© Gooligum Electronics 2015 www.gooligum.com.au

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


CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> single active-high PWM output on P1A

/*** Main loop ***/


for (;;)
{
// sample analog input
ADCON0bits.GO = 1; // start conversion
while (ADCON0bits.GO_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 11).

Half-bridge output mode


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

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


half-H18 or half-bridge circuit, as illustrated on the right.
The PxA and PxB signals are connected to MOSFETs via drivers.
Although the diagram shows PxA 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 PxA signal is active, the upper
MOSFET is switched on, connecting the load to the +V power supply,
and when PxB is active, the lower MOSFET is switched on,
connecting the load to ground.

17
unless the “programmable dead-band delay” mode is used – see below
18
The reason for the “half-H” name will become apparent when we look at the full-bridge example, later…

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 28
© Gooligum Electronics 2015 www.gooligum.com.au

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 low19.
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 PxA and PxB 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 PxDC<6:0> bits in the PWMxCON 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.

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 PxA and PxB are
configured as active-high outputs, the signals present on each will be complementary, as illustrated below:

PxA

PxB

PxA – PxB

19
Each of the four driver circuits in a L293 (or compatible) IC 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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 29
© Gooligum Electronics 2015 www.gooligum.com.au

If a load is connected directly across the PxA and PxB pins, it will “see” the difference in voltage between
them: PxA – PxB, 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.
The code is almost the same as that used in example 1 to
generate a 4 kHz PWM output, with 50% duty cycle20.
Instead of CCP4, we need to configure both P1A and P1B as outputs:
TRISC = ~(1<<5|1<<4); // configure PORTC as all inputs
// except RC5 and RC4 (P1A, P1B outputs)

We’re now using ECCP1 instead of CCP4, but we can continue to use Timer2 as the PWM timebase:
CCPTMRSbits.C1TSEL = 0b00; // use Timer2 with ECCP1

Finally, we need to configure the ECCP1 module in much the same way as we did the CCP4 modules, except
that we now need to select half-bridge mode with P1M<1:0> = ‘10’ and active-high output pins with
CCP1M<1:0> = ‘00’:
// configure ECCP1
CCP1CONbits.P1M = 0b10; // select half-bridge output mode
// -> P1A, P1B active
CCP1CONbits.DC1B = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1100; // select PWM mode: all active-high
// -> half-bridge output on P1A, P1B,
// (both 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 4!

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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 30
© Gooligum Electronics 2015 www.gooligum.com.au

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:

Assuming that the PWM outputs are both configured as active-high, during the active phase of the PWM
signal, P2A will be high and therefore the L293D’s ‘2Y’ output will be driven high. At the same time, P2B
will be low, driving the ‘1Y’ output low. Current will flow from 2Y to 1Y, causing the motor to attempt to
turn in the forward direction.
During the inactive PWM phase, P2A will be low and P2B will be high, driving the ‘2Y’ output low and
‘1Y’ high. Current will now flow in the reverse direction, from 1Y to 2Y, 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 P2A is always high, P2B is always low and current only flows in the forward
direction, from 2Y to 1Y – and the motor will spin forward at maximum speed.
As the other extreme (0% duty cycle), P2A is always low and current only flows in the reverse direction,
from 1Y to 2Y – driving the motor in reverse at maximum speed.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 31
© Gooligum Electronics 2015 www.gooligum.com.au

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 2 and 3.
If you have the Gooligum training board, you can build the circuit in a similar way to that in example 3,
connecting the SN754410 (or L293D) IC and motor supplied in the motor control kit to signals on the 16-pin
header: P2A on pin 7 (‘RC3’), P2B on pin 11 (‘RC2’), +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 3 to power the whole circuit.

The program code is essentially the same as that in examples 2 and 3, with only a few differences.

We’re now using ECCP2 in half-bridge mode, so we need to configure both P2A and P2B as outputs:
// configure ports
TRISC = ~(1<<3|1<<2); // configure PORTC as all inputs
// except RC3 and RC3 (P2A, P2B outputs)

We’ll use Timer2 as the PWM timebase21 for ECCP2:


// select PWM timer
CCPTMRSbits.C2TSEL = 0b00; // use Timer2 with ECCP2

In example 3 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.
We can easily achieve this by reducing the timer 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

21
once again, we could just as easily use Timer4 or Timer6, as we did in examples 2 and 3

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 32
© Gooligum Electronics 2015 www.gooligum.com.au

And finally we need to select half-bridge output mode, with active-high pins, when configuring the ECCP
module:
// configure ECCP2
CCP2CONbits.P2M = 0b10; // half-bridge output, P2A, P2B active
CCP2CONbits.DC2B = 0b00; // LSBs of PWM duty cycle = 00
CCP2CONbits.CCP2M = 0b1100; // PWM mode: all active-high
// -> half-bridge output mode,
// P2A, P2B active-high

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

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

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


// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = OFF

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


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

// configure ports
TRISC = ~(1<<3|1<<2); // configure PORTC as all inputs
// except RC3 and RC3 (P2A, P2B outputs)
ANSELA = 1<<0; // select analog mode for RA0
// -> RA0/AN0 is an analog input

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 33
© Gooligum Electronics 2015 www.gooligum.com.au

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// select PWM timer
CCPTMRSbits.C2TSEL = 0b00; // use Timer2 with ECCP2
// 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 ECCP2
CCP2CONbits.P2M = 0b10; // half-bridge output, P2A, P2B active
CCP2CONbits.DC2B = 0b00; // LSBs of PWM duty cycle = 00
CCP2CONbits.CCP2M = 0b1100; // PWM mode: all active-high
// -> half-bridge output mode,
// P2A, P2B active-high

/*** Main loop ***/


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

// set new PWM duty cycle


CCPR2L = ADRESH; // 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.

A common way of achieving this is with a full-bridge circuit, as illustrated on the next page.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 34
© Gooligum Electronics 2015 www.gooligum.com.au

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

22
recall that, on the PIC16F1824, full-bridge mode is only available with ECCP1

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 35
© Gooligum Electronics 2015 www.gooligum.com.au

Note that “brake mode” is not provided by the ECCP module; to operate the H-bridge in this way, the ECCP
module should be disabled (by clearing the CCPxM bits) and the PWM outputs configured directly by
setting or clearing the output pins (RC2, RC3, RC4 and RC5 for ECCP1) 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 3, 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 3. 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.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 36
© Gooligum Electronics 2015 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 example 3 – the only difference is that we’re
using a different PWM mode, and different output pins.

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)

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 37
© Gooligum Electronics 2015 www.gooligum.com.au

We need to configure a timer for use as the ECCP1 PWM timebase – let’s use Timer4 and, as we did in
example 3, we’ll use a PWM frequency of 244 Hz, because it’s appropriate for the motor supplied with the
Gooligum motor control kit:
// select PWM timer
CCPTMRSbits.C1TSEL = 0b01; // use Timer4 with ECCP1

// configure Timer4
T4CONbits.T4CKPS = 0b10; // prescale = 16
T4CONbits.TMR4ON = 1; // enable timer
// -> TMR4 increments every 16 us
PR4 = 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 ECCP1
CCP1CONbits.P1M = 0b01; // select full-bridge output forward mode
// -> PID modulated, P1A active,
// P1B, PIC inactive
CCP1CONbits.DC1B = 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 ECCP1
CCP1CONbits.P1M = 0b11; // select full-bridge output reverse mode
// -> PIB modulated, P1C active,
// P1A, PID inactive
CCP1CONbits.DC1B = 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)

Finally, since we’re now using ECCP1, we’ll copy the 8-bit ADC result to CCPR1L in the main loop:
// set new PWM duty cycle
CCPR1L = ADRESH; // duty cycle = high byte of ADC result / 256

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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 38
© Gooligum Electronics 2015 www.gooligum.com.au

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 motor23.
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
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 ECCP1 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 ECCP1
CCP1CONbits.P1M0 = 1; // select full-bridge output mode
// -> PIA, P1B, P1C, P1D are PWM outputs
CCP1CONbits.DC1B = 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…).

23
and of course other types of motor

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 39
© Gooligum Electronics 2015 www.gooligum.com.au

So we want:
Motor full speed reverse: ADRESH = 0
Motor stopped: ADRESH = 127 or 128
Motor full speed forward: ADRESH = 255

We can express this as:


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

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 40
© Gooligum Electronics 2015 www.gooligum.com.au

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.

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)

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 41
© Gooligum Electronics 2015 www.gooligum.com.au

Complete program
Here is the full source code, showing how all these pieces fit together:
/************************************************************************
* Description: Lesson 15, 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) *
* *
************************************************************************/

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

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


// ext reset, internal oscillator (no clock out), 4xPLL off
#pragma config MCLRE = ON, FOSC = INTOSC, CLKOUTEN = OFF, PLLEN = OFF
// no watchdog timer, brownout resets enabled, low brownout voltage
#pragma config WDTE = OFF, BOREN = ON, BORV = LO
// no power-up timer, no failsafe clock monitor, two-speed start-up disabled
#pragma config PWRTE = OFF, FCMEN = OFF, IESO = OFF
// no code or data protect, no write protection
#pragma config CP = OFF, CPD = OFF, WRT = OFF
// stack resets on, high-voltage programming
#pragma config STVREN = ON, LVP = OFF

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


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

// configure ports
TRISC = 0b000011; // configure PORTC as all inputs
// except RC2-5 (P1A-D outputs)

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 42
© Gooligum Electronics 2015 www.gooligum.com.au

ANSELA = 1<<0; // select analog mode for RA0


// -> RA0/AN0 is an analog input

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b1101; // internal oscillator = 4 MHz
// -> 1 us / instruction cycle

// configure ADC
ADCON1bits.ADCS = 0b001; // Tad = 8*Tosc = 2 us (with Fosc = 4 MHz)
ADCON1bits.ADFM = 0; // MSB of result in ADRESH<7>
ADCON1bits.ADNREF = 0; // Vref- is Vss
ADCON1bits.ADPREF = 0b00; // Vref+ is Vdd
ADCON0bits.CHS = 0b00000; // select channel AN0
ADCON0bits.ADON = 1; // turn ADC on

// Setup PWM
// select PWM timer
CCPTMRSbits.C1TSEL = 0b01; // use Timer4 with ECCP1
// configure Timer4
T4CONbits.T4CKPS = 0b10; // prescale = 16
T4CONbits.TMR4ON = 1; // enable timer
// -> TMR4 increments every 16 us
PR4 = 255; // period = 256 x 16 us = 4096 us
// -> PWM frequency = 244 Hz
// configure ECCP1
CCP1CONbits.P1M0 = 1; // select full-bridge output mode
// -> PIA, P1B, P1C, P1D are PWM outputs
CCP1CONbits.DC1B = 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.GO_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)
}

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 43
© Gooligum Electronics 2015 www.gooligum.com.au

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)
// P1C active (low)
// P1A inactive (low)
// P1D inactive (high)
}
}
}

Single-output mode with PWM steering


We saw earlier that, by default, an enhanced CCP module in single-output PWM mode will output the PWM
signal on its PxA pin.
The ECCP modules’ single-output mode is actually a little more flexible than that – a feature called “PWM
steering”24 allows the PWM signal to be directed to any of the module’s PxA – PxD pins.
PWM steering for each ECCP module is controlled by a PSTRxCON register (where ‘x’ is ‘1’ or ‘2’):

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


PSTRxCON - - - STRxSYNC STRxD STRxC STRxB STRxA

The STRxA - STRxD bits select whether the PWM signal will appear on the corresponding PxA – PxD pin.
If one (or more) of these bits is set to ‘1’, the PWM signal will be output on the corresponding pin(s).
Otherwise, the pin is available for general I/O.

Note that, because the ECCP2 module on the PIC16F1824 has only two output pins (P2A and P2B), the
STR2C and STR2D bits are unused (they don’t do anything) in the ECCP2 module’s PSTR2CON register.
Note also that PWM steering is only available in single-output mode, and only for ECCP modules.

In PWM steering more, the CCPxM<1:0> bits continue to configure each PxA – PxD output as being
active-high or active-low, as before.
Hence, PWM steering can be used to output a “normal” (active-high) PWM signal on one or more pins,
while outputting an “inverted” (active-low) version of the same signal on one or more other pins.
Like half-bridge mode, PWM steering allows us to output a PWM signal on one pin, and a complementary
PWM signal on another pin – with the advantage that we can now choose (assuming we’re using ECCP1)
which two pins to use out of P1A – P1D; we’re no longer limited to using P1A and P1B. On the other

24
also referred to as “pulse steering” in some documentation

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 44
© Gooligum Electronics 2015 www.gooligum.com.au

hand, half-bridge mode offers dead-band delay, which is very important in some applications, especially
those involving high-power switches, while PWM steering does not.

Finally, the STRxSYNC bit affects when any change to the steering configuration will take place; if it is
cleared to ‘0’, the output pins react immediately whenever the STRxA – STRxD bits are updated, but if
STRxSYNC is set to ‘1’, the output pins won’t change until the end of the current PWM cycle, ensuring that
a complete PWM waveform is output.

Example 9: Using PWM steering to increase effective PWM voltage


To demonstrate PWM steering, we’ll re-implement example 5, where we used half-bridge output mode to
generate a complementary PWM waveform to drive a piezo speaker with a higher effective voltage.
In this example we’ll use PWM steering with the same circuit to generate the same waveforms on the same
output pins (P1A and P1B).
We’ll direct the “normal” (active-high) PWM output to P1A and an inverted (active-low) version of that
signal to P1B.

To do so, we only need to change the ECCP module configuration.


First, select the pins to be used for PWM steering:
PSTR1CONbits.STR1A = 1; // enable single PWM output on P1A
PSTR1CONbits.STR1B = 1; // and P1B

and then enable single-output PWM, with P1A active-high and P1B active-low:
CCP1CONbits.P1M = 0b00; // select single output mode
// -> P1A, P1B active
CCP1CONbits.DC1B = 0b00; // LSBs of PWM duty cycle = 00
CCP1CONbits.CCP1M = 0b1101; // select PWM mode: P1A, P1C active-high
// P1B, P1D active-low
// -> single PWM output on P1A,
// inverted PWM output on P1B

Those are the only changes; the rest of the program is unchanged from example 5.
You should find that the circuit works (sounding the piezo loudly) exactly the same as before.

The half-bridge version from example 5 was two statements shorter, because we didn’t have to setup the
PSTR1CON register, but this way, using PWM steering, we have the flexibility to instead use the P1C
and/or P1D pins, if we wish. Both methods are valid...

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 45
© Gooligum Electronics 2015 www.gooligum.com.au

Conclusion
This has been another very long lesson, especially for one that explores only a single feature of the CCP and
ECCP modules: 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.

In our next lesson we’ll see how the 16F1824’s EEPROM and Flash memories can be used to preserve data
when the PIC is powered off.

Enhanced Mid-Range PIC C, Lesson 15: CCP, part 2 – Pulse-Width Modulation Page 46
© Gooligum Electronics 2015 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 PIC 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

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

You might also like