You are on page 1of 20

© Gooligum Electronics 2014 www.gooligum.com.

au

Introduction to PIC Programming


Programming Enhanced Mid-Range PICs in C

by David Meiklejohn, Gooligum Electronics

Lesson 9: Voltage Reference and DAC

The previous lesson included examples of a comparators being used to compare a variable input signal with a
fixed threshold, or reference voltage, which was generated by an external voltage divider. The need for a
reference voltage, whether for use with a comparator or analog-to-digital converter (ADC), is so common
that many enhanced mid-range PICs include a fixed voltage reference, which, as we’ll see in this lesson, can
be used with the comparator module – avoiding the need to use an external reference.
Many enhanced mid-range PICs also include a digital-to-analog converter (DAC) 1, which generates a
programmable voltage which can be made available externally as an analog output, or, as we will see in this
lesson, used internally with peripherals including the comparator module.
In summary, this lesson covers:
 Configuring the fixed voltage reference
 Using the digital-to-analog converter
 Making the DAC output available externally
 Using the fixed voltage reference and DAC as comparator reference (threshold) inputs
with examples implemented using XC8 (running in “Free mode”).

Fixed Voltage Reference


The fixed voltage reference (FVR) module on the PIC16F1824 consists of a (nominally) 1.024 V reference
which can be multiplied by 1, 2 or 4 times to generate a fixed 1.024 V, 2.048 V or 4.096 V reference for use
with the ADC module2. A second voltage multiplier can be configured to supply one of these three reference
voltages for use with the comparators and/or DAC.
It is controlled by the FVRCON register:

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


FVRCON FVREN FVRRDY TSEN TSRNG CDAFVR<1:0> ADFVR<1:0>

1
Some enhanced mid-range PICs, such as the 16F1704, inlcude an 8-bit DAC, which operates in a very similar way to
the 5-bit DAC module described in this lesson.
2
The analog-to-digital converter will be covered in a future lesson, but note for now that these reference voltages are all
“powers of two”: 1.024 V = 210 mV. Thus, these reference voltages make it easy to convert signal levels measured by
the ADC (in binary) to actual voltages.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 1


© Gooligum Electronics 2014 www.gooligum.com.au

The CDAFVR bits select the reference voltage to be supplied to the comparators and DAC:
The voltages shown here are nominal;
according to the PIC16F1824 data sheet the CDAFVR<1:0> Multiplier Reference Voltage
actual levels could vary up to 11% from 00 off none (output is off)
these nominal values – which is not a very
accurate voltage reference at all! 01 1x 1.024 V

However, that is a worst-case figure which 10 2x 2.048 V


applies across the device’s rated 11 4x 4.096 V
temperature range3. It’s much more
accurate at room temperature.
For example, the “1.024 V” reference on a 16F1824, chosen at random, was measured a 1.026 V, which is a
difference of less than 0.2%.
Note that the reference voltage cannot exceed the supply voltage, VDD.

The ADFVR bits are used in the same way to select the reference voltage to be supplied to the ADC module.

The FVREN bit enables the fixed voltage reference module – clearing it will disable the FVR, reducing
power consumption (useful in sleep mode, unless of course you are using the FVR in conjunction with the
comparator module to wake the device from sleep…).
FVRRDY is a flag which indicates that the FVR output is ready for use. This is always true on the 16F1824,
but not on the low-power 16LF1824 variant, where the FVR requires time to stabilise after being enabled.

Example 1: Comparator with fixed voltage reference


To illustrate how the fixed voltage reference can
provide the threshold voltage for a comparator,
we’ll re-implement the second example from
lesson 8, but without the external voltage divider,
as shown in the circuit on the right.
Once again, the LDR and 22 kΩ resistor generate
a voltage on the C12IN0- input which rises as the
light level increases. And of course you can use a
potentiometer (or any sensor with a 0 – 5 V
analog output) if you don’t have a photocell.
If you are using the Gooligum training board you
can build this circuit by placing a shunt in position
3 (labelled ‘C1IN-’) of JP25, connecting a
photocell (PH2) and 22 kΩ resister to C12IN0-,
and in JP17 to enable the LED on RC1.

As before, we’ll light the LED on RC1 when the

3
Although variation with temperature is unwanted in a reference voltage, it can be used to infer the device’s
temperature. This effect is utilised by the 16F1824’s temperature indicator module, which is controlled by the TSEN
and TSRNG bits in the FVRCON register – see the data sheet for details.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 2


© Gooligum Electronics 2014 www.gooligum.com.au

photocell is in darkness, corresponding to the input voltage on C12IN0- being less than the threshold level,
which in this example will be generated by the FVR instead of external resistors.
We don’t have a lot of choices for the reference voltage: only 1.024 V, 2.048 V or 4.096 V. Given that we’re
using a 5 V supply and that the LDR/resistor combination will generate around half the supply voltage for
“medium” light levels, a threshold of 2.048 V seems appropriate.

The program code is essentially the same as in the example from lesson 8, except that we now need
configure the FVR module to output 2.048 V:
// configure fixed voltage reference
FVRCONbits.FVREN = 1; // FVR enabled
FVRCONbits.CDAFVR = 0b10; // Comp+DAC output is 2x (= 2.048 V)
// -> output 2.048 V to comparators

Of course we also need to configure the comparator to use the FVR as its positive input:
// configure comparator 2
CM2CON0bits.C2ON = 1; // comparator enabled
CM2CON0bits.C2OE = 0; // external output disabled
CM2CON0bits.C2POL = 0; // output not inverted
CM2CON0bits.C2SP = 1; // normal power mode
CM2CON0bits.C2HYS = 1; // hysteresis enabled
CM2CON0bits.C2SYNC = 0; // asynchronous output
CM2CON1bits.C2PCH = 0b10; // + in = fixed voltage ref
CM2CON1bits.C2NCH = 0b00; // - in = C12IN0- pin
// -> C2OUT = 1 if C12IN0- < 2.048 V

Note that we no longer need to enable the external output, because we’re no longer using it to generate
hysteresis – which we’ve enabled within the comparator module instead4. Therefore it’s also no longer
necessary to configure RC4 as an output (because we’re not using C2OUT). And, since we’re not using the
C2IN+ input, there is no need to configure RC0 as an analog input.

The port initialisation code becomes, simply:


// configure ports
TRISC = 0b111101; // configure RC1 (only) as an output
// (RA1/C12IN0- is an input)
ANSELAbits.ANSA1 = 1; // select analog mode for RA1/C12IN0-

Finally, note that we could have just as easily used comparator 1 instead, configured in exactly the same way
as comparator 2 was here.

4
If the internal comparator hysteresis is inadequate, it is still possible to use the external comparator output to generate
hysteresis, even when using an internal voltage reference. The comparator output has to be fed back into the signal
being measured (on the comparator’s negative input) instead of the threshold voltage, and the comparator operation
must therefore be inverted to ensure that the feedback, required to generate hysteresis, is positive.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 3


© Gooligum Electronics 2014 www.gooligum.com.au

Complete program
The rest of the program is essentially the same as in lesson 8, as listed below:
/************************************************************************
* Description: Lesson 9, example 1 *
* *
* Demonstrates basic use of comparator with fixed voltage reference *
* *
* Turns on LED when input on C12IN0- < 2.048 V *
* *
*************************************************************************
* Pin assignments: *
* C12IN0- = voltage to be measured (e.g. pot output or LDR) *
* RC1 = indicator LED *
* *
************************************************************************/

#include <xc.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

// Pin assignments
#define LED LATCbits.LATC1 // indicator LED

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


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

// configure ports
TRISC = 0b111101; // configure RC1 (only) as an output
// (RA1/C12IN0- is an input)
ANSELAbits.ANSA1 = 1; // select analog mode for RA1/C12IN0-

// configure fixed voltage reference


FVRCONbits.FVREN = 1; // FVR enabled
FVRCONbits.CDAFVR = 0b10; // Comp+DAC output is 2x (= 2.048 V)
// -> output 2.048 V to comparators

// configure comparator 2
CM2CON0bits.C2ON = 1; // comparator enabled
CM2CON0bits.C2OE = 0; // external output disabled
CM2CON0bits.C2POL = 0; // output not inverted
CM2CON0bits.C2SP = 1; // normal power mode
CM2CON0bits.C2HYS = 1; // hysteresis enabled
CM2CON0bits.C2SYNC = 0; // asynchronous output
CM2CON1bits.C2PCH = 0b10; // + in = fixed voltage ref
CM2CON1bits.C2NCH = 0b00; // - in = C12IN0- pin
// -> C2OUT = 1 if C12IN0- < 2.048 V

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 4


© Gooligum Electronics 2014 www.gooligum.com.au

/*** Main loop ***/


for (;;)
{
// continually display comparator output
LED = CM2CON0bits.C2OUT;
}
}

The key difference between this program and the one presented in lesson 8 is that the RC0 pin is now
available for use.

Digital-to-Analog Converter
The digital-to-analog converter (DAC) module in the PIC16F1824 can be used to generate an (analog) output
voltage proportional to a selectable 5-bit value.
The DAC output is derived from an internal resistor ladder connected between a positive voltage source
(either VDD, an external reference, or the internal fixed reference) and a negative source (VSS or an external
reference).
It can be used internally with peripherals including the comparators and ADC, and can be made available
externally via the DACOUT pin, which on the 16F1824 is shared with (and takes priority over) RA0.

The DAC is controlled by the DACCON0 register:


Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
DACCON0 DACEN DACLPS DACOE – DACPSS<1:0> – DACNSS

The DACPSS bits select the positive voltage source:

DACPSS<1:0> DAC Positive Voltage Source Note that the positive voltage source can be
taken from the VREF+ pin, allowing you to
00 VDD derive the DAC output from a (possibly
01 VREF+ pin (shared with RA1) more accurate) external voltage reference.

10 FVR (comparator + DAC output) The VREF+ pin is shared with RA1 on the
16F1824, so to use it as the DAC positive
11 reserved (do not use) voltage source you must configure the pin as
an analog input, by setting TRISA<1> and
ANSELA<1>.
The DACNSS bit selects the negative voltage source:

DACNSS DAC Negative Voltage Source Similarly, the negative voltage source can be
taken from an external reference connected
0 VSS (usually ground) to the VREF- pin, which on the 16F1824 is
1 VREF- pin (shared with RA0) shared with RA0, so to use it with the DAC
you must configure the RA0 pin as an
analog input.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 5


© Gooligum Electronics 2014 www.gooligum.com.au

So if, for example, the DAC is configured with VDD and VSS as the positive and negative sources, and VDD
= 5 V and VSS = 0 V, the DAC output will vary between 0 V and 5 V.
But if the VREF+ and VREF- pins are selected as the positive and negative sources, and VREF+ = 4 V and
VREF- = 3 V, the DAC output would vary between 3 V and 4 V.

The DACEN bit enables the DAC module, connecting the resistor ladder between the positive and negative
voltage sources – and therefore consuming current.

To save power, you may wish to disable the DAC by clearing DACEN. This disconnects the resistor ladder,
so that current no longer flows through it – but it is only disconnected at one end.
The DACLPS bit selects which end of the resistor ladder remains connected when the DAC is in its low-
power state:
DACLPS = 1 selects the positive voltage source
DACLPS = 0 selects the negative voltage source

The DACOE bit enables the DAC external output on the DACOUT pin (RA0).

Note that, if enabled, the external DAC output remains connected, even if the DAC itself is disabled (in low-
power mode). This provides a way to make the FVR output available externally:
 select the FVR as the DAC positive voltage source (DACPSS = 10)
 select Vss as the DAC negative voltage source (DACNSS = 0)
 disable the DAC, selecting low-power mode (DACEN = 0)
 connect the resistor ladder to the positive source (DACLPS = 1)
 enable the external output (DACOE = 1)
The FVR’s comparator and DAC output will now appear on the DACOUT pin5.

The DAC output voltage is specified by the DACR bits in the DACCON1 register:
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
DACCON1 – – – DACR<4:0>

The DAC output is proportional to this 5-bit DACR value6, within the range bounded by the positive and
negative voltage sources.

Or, more formally:

For example, if the FVR and VSS are selected as the positive and negative sources, VSS = 0 V and the FVR
output is configured to be 2.048 V, then the DAC output VOUT = DACR × 2.048 V / 32 = DACR × 64 mV.

5
This is how the FVR output was measured earlier
6
In enhanced mid-range PICs with an 8-bit DAC, DACR is an 8-bit quantity, filling the DACCON1 register.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 6


© Gooligum Electronics 2014 www.gooligum.com.au

Thus, for various values of DACR we would have:


DACR = 0 → VOUT = 0.000 V
DACR = 10 → VOUT = 0.640 V
DACR = 16 → VOUT = 1.024 V
DACR = 20 → VOUT = 1.280 V
DACR = 31 → VOUT = 1.984 V

Note that the maximum DAC output voltage, with DACR = 31, is always a little less than the positive source
voltage, unless you disable the DAC, in which case the output can be connected to either the positive or
negative source, as described above.

Example 2: External DAC output


To “see” the DAC output as it varies, it’s easiest to use
an oscilloscope to view the voltage on the DACOUT
pin, which we can do with the (very simple) circuit
shown on the right.
The 1 nF capacitor is optional, as we’ll see later.

If you have the Gooligum training board, you can


access the DACOUT pin via pin 8 (‘GP/RA/RB0’) on
the 16-pin header. You can then use the solderless
breadboard to connect the supplied 1 nF capacitor to
ground (pin 16, ‘GND’). No jumpers on the board need
to be closed.

To demonstrate the DAC output we’ll generate a


sawtooth waveform, ramping from 0 V to VDD (5 V),
returning immediately to 0 V and repeating.

We want the output to vary between 0 V and VDD, so we’ll configure the DAC with VDD as the positive
source and VSS as the negative source:
// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 1; // DACOUT pin enabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
// -> output DACR/32*Vdd on DACOUT
DACCON1bits.DACR = 0; // initial output = 0 V

Note that the external output has been enabled by setting DACOE, and that the output has been set initially
to 0 V by clearing DACR to 0.

Note also that there is no need to configure the RA0 pin, which DACOUT shares, as an output, because the
DACOUT function (if enabled) takes priority over RA0.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 7


© Gooligum Electronics 2014 www.gooligum.com.au

To generate a ramp, we need to continually increment the value in DACR from 0 to 31, which we could do
with a for loop such as:
for (dac_out = 0; dac_out < 32; dac_out++)
DACCON1bits.DACR = dac_out;

That’s very clear, but quite slow – when compiled with v1.30 of the XC8 compiler (running in the
unoptimised “Free mode”), and with the processor running at the default 500 kHz, this loop completes in
4.14 ms, generating a waveform with a frequency of 241 Hz.
It would be possible to do without the loop variable, incrementing DACR directly within the for loop.
But since DACR is a 5-bit field, if we continue to increment it, it will overflow on its own from 31 back to 0.
So we could use as the main loop:
for (;;)
{
// continually generate sawtooth (rising ramp) waveform
DACCON1bits.DACR++; // increment DAC output level
}

This executes more quickly – DACR now increments from 0 to 31, then back to 0 every 2.82 ms, giving the
sawtooth output a frequency of 355 Hz.
This can be sped up further by noting that DACR is a field within the DACCON1 register, and the upper bits
in DACCON1 register are not used. This means that we can simply increment the DACCON1 register,
which has the effect of incrementing DACR. There is no need to worry about or handle the overflow from
DACR = 31 back to 0, because, as mentioned, the upper bits of DACCON1 are not used.
So, generating a ramped output can be as simple as:
for (;;)
{
// continually generate sawtooth (rising ramp) waveform
DACCON1++; // increment DAC output level (DACR)
}

This executes even more quickly, giving an output frequency of 651 Hz.
Again, that’s when using the XC8 compiler in “Free mode”, where much of the compiler optimisation is
disabled. The code would be faster if it was more optimised, meaning that the output waveform will have a
higher frequency if this code is compiled with a paid-for version of the XC8 (or other) compiler. Indeed, the
code generation is likely to differ between different versions of the same compiler. So, for predictable
results, it would be better to use a timer (usually in conjunction with an interrupt – see lesson 5), although the
processor would then have to be clocked much faster, because of the extra software overhead.
An alternative for time-critical code such as this is to write it in assembly language. For example, the
assembly language version of this program, in enhanced mid-range assembler lesson 11, generated a
sawtooth wave with a frequency of 977 Hz.

Complete program
Here is the full (but quite short) program listing:
/************************************************************************
* Description: Lesson 9, example 2 *
* *
* Outputs sawtooth (rising ramp) waveform on DACOUT *
* F = ~650 Hz, V = 0 - 0.97*Vdd *
* *

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 8


© Gooligum Electronics 2014 www.gooligum.com.au

*************************************************************************
* *
* Pin assignments: *
* DACOUT (RA0) = sawtooth output *
* *
************************************************************************/

#include <xc.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 oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b0111; // internal oscillator = 500 kHz
// -> 8 us / instruction cycle

// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 1; // DACOUT pin enabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
// -> output DACR/32*Vdd on DACOUT
DACCON1bits.DACR = 0; // initial output = 0 V

/*** Main loop ***/


for (;;)
{
// continually generate sawtooth (rising ramp) waveform
// 6 inst cycles/increment * 8 us/inst cycle = 48 us/increment
// 32 increments/period * 48 us/increment = 1536 us/period
// -> Fout = 651 Hz
DACCON1++; // increment DAC output level (DACR)
}
}

Note again that, because we’re only using DACOUT, there is no need for our usual port initialisation code.
And the oscillator initialisation could have been omitted, since all we are doing here is confirming the default
internal RC oscillator configuration – making this program very short indeed.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 9


© Gooligum Electronics 2014 www.gooligum.com.au

Here is an oscilloscope plot of the output on the DACOUT pin7, with the 1 nF capacitor removed from the
circuit:

The stepped output (32 steps per period) is clearly visible.

It’s also apparent that the steps are sharper toward the limits (VSS and VDD) of the sawtooth, and rounded
toward the middle of the output range.
This happens because, as explained earlier, the DAC output is generated from an internal resistor ladder,
which on the 16F1824 consists of 32 resistors, nominally 5 kΩ each.
This presents an output impedance which increases toward the middle of the ladder, where more resistors are
in series between the selected output tap and the positive or negative source, compared with the edges of the
ladder where only a few resistors are involved.
This high output impedance combines with stray capacitance to create a low-pass filter, smoothing the output
the most in the middle of the output range, where the impedance is highest.
In fact, given the high impedance (and hence limited drive capability) of the DAC output, it is advisable to
buffer it with e.g. an op-amp instead of driving external circuits directly.

Of course, you would usually want to remove these steps from the output, to reduce the quantization noise.

7
These plots were generated using the assembly language version of this program from enhanced mid-range assembler
lesson 11, and therefore show a 977 Hz waveform, instead of the 651 Hz sawtooth produced by this example (using
XC8 v1.30 in “Free mode”). However, the 651 Hz versions have the same general form – the same observations apply.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 10


© Gooligum Electronics 2014 www.gooligum.com.au

This can be done, to a limited extent, with a simple RC filter on the output. Of course, too much filtering
will distort the sawtooth shape. But in general it’s common to want to significantly filter frequencies higher
than the sampling frequency, which is 1/(32 µs) = 31 kHz in this case.
In theory, we’d add a suitable resistor between the output pin and the filter capacitor, to form the ‘R’ in the
RC filter. But in practice, this isn’t needed because, as explained above, the DAC output already has
significant internal output impedance – there is no need to add more resistance outside the pin.
Adding a 1 nF capacitor for filtering, as shown in the circuit diagram, significantly smooths the output, as
shown:

Note that some ripple due to the quantization steps is still visible at the extremes, where the output
impedance (and hence filter cut-off frequency) is lowest.
To do this “properly” we’d use more elaborate filtering methods, in conjunction with buffering, but on the
other hand if fidelity is important it doesn’t really make much sense to be using a simple 5-bit DAC in the
first place!

Example 3: Comparator with variable voltage reference (DAC)


We saw in example 1 that the fixed voltage reference can be used as a comparator input, avoiding the need to
use an external pin to provide the threshold voltage. We also saw in that example that the fixed voltage
reference, being “fixed”, does not provide much flexibility, offering only three selectable output levels.
The DAC can be used in combination with the FVR to provide a much greater selection of voltages to the
comparators, without sacrificing the accuracy, stability or convenience of the FVR.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 11


© Gooligum Electronics 2014 www.gooligum.com.au

To show how the DAC can be used with the comparators, we could simply re-implement example 1, with the
threshold voltage (perhaps selecting 2.5 V instead of 2.048 V) generated by the DAC.
However, given that the DAC provides a variable (or programmable) voltage reference, we can use it to test
an input signal against more than one level – that is, that the signal is within defined limits (implementing a
window comparator).

We will add a second LED to the circuit from


example 1, as shown on the right.
The LED on RC3 will indicate a low level of
illumination, and the LED on RC1 will
indicate bright light. When neither LED is lit,
the light level will be in the middle; not too
dim or too bright.

To implement this circuit with the Gooligum


training board, place shunts in position 3
(labelled ‘C1IN-’) of JP25, connecting a
photocell (PH2) and 22 kΩ resister to
C12IN0-, and in JP17 and JP19 to enable the
LEDs on RC1 and RC3.

To test whether the input is within limits, we will first configure the DAC to generate the “low” threshold
voltage, compare the input on C12IN0- with this low level, and then reconfigure the DAC to generate the
“high” threshold and compare the input with this higher level.
This process could be extended to multiple input thresholds, by configuring the DAC to generate each
threshold in turn. However, if you wish to test against more than a few threshold levels, you would probably
be better off using an analog-to-digital converter (to be described in a later lesson).
This example uses 2.0 V as the “low” threshold and 3.0 V as the “high” threshold, but, since the DAC is
programmable, you can always choose your own levels!

In this example it makes sense to use the 5 V supply (VDD) as the positive source for the DAC, because the
input signal from the LDR is derived from, and varies linearly with, VDD.
So we configure the DAC as in the last example, but without the external output enabled:
// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 0; // DACOUT pin disabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
// -> DAC out = DACR/32*Vdd
// = DACR*156 mV (if Vdd = 5.0 V)

But if you prefer, you could use the FVR as the positive source – simply configure it similarly to in the first
example, except with a 4.096 V output to the DAC selected (CDAFVR = 11), and then select the FVR as the
positive source in the DAC configuration (DACPSS = 10). This change would of course affect the voltage
calculations, below, because the DAC positive source would be 4.096 V instead of 5.0 V.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 12


© Gooligum Electronics 2014 www.gooligum.com.au

To demonstrate that either comparator can be used, we’ll use comparator 1 here, configured the same way
that comparator 2 was in the first example, except that we now have to select the DAC as the positive
comparator input:
// configure comparator 1
CM1CON0bits.C1ON = 1; // comparator enabled
CM1CON0bits.C1OE = 0; // external output disabled
CM1CON0bits.C1POL = 0; // output not inverted
CM1CON0bits.C1SP = 1; // normal power mode
CM1CON0bits.C1HYS = 1; // hysteresis enabled
CM1CON0bits.C1SYNC = 0; // asynchronous output
CM1CON1bits.C1PCH = 0b01; // + in = DAC output
CM1CON1bits.C1NCH = 0b00; // - in = C12IN0- pin
// -> C1OUT = 1 if C12IN0- < DAC

The DAC can be set to generate approximately 2.0 V, by:


// set low input threshold
DACCON1bits.DACR = 13; // DAC out = 13*156 mV = 2.03 V

The closest match to 3.0 V is obtained by:


// set high input threshold
DACCON1bits.DACR = 19; // DAC out = 19*156 mV = 2.97 V

After changing the DAC output, it can take a little while for it to settle and stably generate the newly-
selected voltage. According to the PIC16F1824 data sheet, this settling time can be up to 10 µs.
Therefore, we should insert a 10 µs delay (using the ‘__delay_us()’ macro) after selecting the DAC
voltage, before using the comparator.

Complete program
Here is how these code fragments fit into the framework of the program from example 1:
/************************************************************************
* *
* Description: Lesson 9, example 3 *
* *
* Demonstrates basic use of comparator with DAC *
* *
* Turns on Low LED when C12IN0- < 2.0 V (low light level) *
* or High LED when C12IN0- > 3.0 V (high light level) *
* *
*************************************************************************
* *
* Pin assignments: *
* C12IN0- = voltage to be measured (e.g. pot output or LDR) *
* RC3 = "Low" LED *
* RC1 = "High" LED *
* *
************************************************************************/

#include <xc.h>

#define _XTAL_FREQ 500000 // oscillator frequency for _delay()

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 13


© Gooligum Electronics 2014 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

// Pin assignments
#define HI LATCbits.LATC1 // "High" LED
#define LO LATCbits.LATC3 // "Low" LED

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


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

// configure ports
TRISC = 0b110101; // configure RC1 and RC3 as outputs
// (RA1/C12IN0- is an input)
ANSELAbits.ANSA1 = 1; // select analog mode for RA1/C12IN0-

// configure oscillator
OSCCONbits.SCS1 = 1; // select internal clock
OSCCONbits.IRCF = 0b0111; // internal oscillator = 500 kHz

// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 0; // DACOUT pin disabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
// -> DAC out = DACR/32*Vdd
// = DACR*156 mV (if Vdd = 5.0 V)

// configure comparator 1
CM1CON0bits.C1ON = 1; // comparator enabled
CM1CON0bits.C1OE = 0; // external output disabled
CM1CON0bits.C1POL = 0; // output not inverted
CM1CON0bits.C1SP = 1; // normal power mode
CM1CON0bits.C1HYS = 1; // hysteresis enabled
CM1CON0bits.C1SYNC = 0; // asynchronous output
CM1CON1bits.C1PCH = 0b01; // + in = DAC output
CM1CON1bits.C1NCH = 0b00; // - in = C12IN0- pin
// -> C1OUT = 1 if C12IN0- < DAC

/*** Main loop ***/


for (;;)
{
/*** Test for low illumination ***/
// set low input threshold
DACCON1bits.DACR = 13; // DAC out = 13*156 mV = 2.03 V
__delay_us(10); // wait 10 us for DAC to settle

// compare with input


LO = CM1CON0bits.C1OUT; // turn on Low LED if C12IN0- < DAC

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 14


© Gooligum Electronics 2014 www.gooligum.com.au

/*** Test for high illumination ***/


// set high input threshold
DACCON1bits.DACR = 19; // DAC out = 19*156 mV = 2.97 V
__delay_us(10); // wait 10 us for DAC to settle

// compare with input


HI = ~CM1CON0bits.C1OUT; // turn on High LED if C12IN0- > DAC
}
}

Example 4: Wake on dual comparator change, using internal voltage references


We saw in lesson 8 that a comparator can be used to wake the PIC from sleep mode, whenever the
comparator’s output changes in a defined way.
Assuming that one of the comparator’s inputs is a fixed reference, defining a threshold level, the
comparator’s output will change, waking the PIC, whenever the second input crosses this threshold.
Thus, the comparator can be used as an “alarm”, waking the PIC whenever a particular input becomes too
high or too low. However, a single comparator can only test for either “too high” or “too low” – not both.
What if you had a signal representing a condition (say, temperature), which had to maintained within a
certain range?
We saw in the last example that it is possible to use a programmable voltage reference (the DAC) with a
single comparator to test an input against two thresholds, by regularly switching the reference between the
two threshold levels and polling the comparator output for each.
But you can’t use that approach to wake the PIC from sleep mode whenever the input goes too high or too
low (we can’t change the DAC output while in sleep mode); instead, you need two comparators.

We can demonstrate how to do that by re-implementing the previous example to use two comparators, with
the PIC in sleep mode while the input is within the “allowed” range of 2 – 3 V. Whenever the input crosses
one of those limits, the device will wake and light the appropriate LED (RC1 for “too high” and RC3 for
“too low”, as before) to indicate the error condition. When the condition clears (the input voltage moves back
into the middle range), the PIC will re-enter sleep mode, until the input again crosses one of the limits.
From the outside, the circuit will seem to be behaving in exactly the same way as in the previous example;
the difference is that the PIC will now be in sleep mode whenever neither LED is lit.

We’re comparing a common signal (the input) against two reference levels, 2.0 V and 3.0 V.
As mentioned, we can’t use the DAC to generate both levels, as we did in the previous example, because
when the PIC is in sleep mode it doesn’t execute any instructions (until woken), leaving us no way to alter
the DAC output level while the PIC is sleeping8.
So we need to use the DAC as a fixed reference – selecting its output voltage before entering sleep mode.

8
other than periodically waking the PIC from sleep, using for example the watchdog timer (see lesson 6), and altering
the DAC output each time the device wakes. The problem with that approach is that the device would no longer
respond (wake) immediately to input level changes – it becomes effectively a slow polling approach. And if the polling
rate is increased by waking the PIC more often, we’re no longer saving much power – missing the point of using sleep
mode in the first place.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 15


© Gooligum Electronics 2014 www.gooligum.com.au

That means that although we can use the DAC to generate one of these thresholds, we will need a second
reference to generate the other.
Luckily, with these threshold values, that’s not a problem – the FVR can be configured as a 2.048 V
reference (as we did in example 1), which is close enough to 2.0 V for our purpose here.
If the FVR wasn’t able to generate an appropriate threshold, we’d have to use an external voltage reference
as one the thresholds, as we did in lesson 8.
That leaves the DAC to generate the second threshold of 3.0 V. As we saw in the previous example, given a
5 V supply and with VDD selected as the positive source, the DAC can be configured to output 2.97 V (by
selecting DACR = 19), which again is near enough to 3.0 V.
So, we configure the fixed voltage reference with:
// configure fixed voltage reference
FVRCONbits.FVREN = 1; // FVR enabled
FVRCONbits.CDAFVR = 0b10; // Comp+DAC output is 2x (= 2.048 V)
// -> output 2.048 V to comparators

And the DAC with:


// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 0; // DACOUT pin disabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
DACCON1bits.DACR = 19; // DAC out = 19/32*Vdd
// = 2.97 V (if Vdd = 5.0 V)

We can then use one of these voltage references with one comparator, and the other reference with the
second comparator.
Recall, from lesson 8, that the comparator negative input pins, including C12IN0-, are shared by the two
comparators. So, for each comparator, we can select the C12IN0- pin as the negative input, and either the
FVR or DAC as the positive input. That means that it doesn’t matter which comparator we use with which
voltage reference – they are interchangeable.
But since we do need to make a choice, we’ll use comparator 1 with the DAC (outputting 2.97 V) to detect
the C12IN0- input crossing the upper threshold, and comparator 2 with the FVR (2.05 V) to detect when
C12IN0- crosses the lower threshold.
We also need to decide whether each comparator output should be inverted – this is completely arbitrary,
because if the comparator polarity is changed, the program logic, where the comparator output is tested, can
be changed appropriately. But in this case we’ll configure each comparator so that a ‘1’ in the output bit
indicates an error condition, i.e. that the input is higher than (for comparator 1) the upper threshold, or lower
than (for comparator 2), the lower threshold.
So, we’ll configure comparator 1 with:
// configure comparator 1
CM1CON0bits.C1ON = 1; // comparator enabled
CM1CON0bits.C1OE = 0; // external output disabled
CM1CON0bits.C1POL = 1; // output inverted
CM1CON0bits.C1SP = 0; // low-power mode
CM1CON0bits.C1HYS = 1; // hysteresis enabled
CM1CON0bits.C1SYNC = 0; // asynchronous output
CM1CON1bits.C1PCH = 0b01; // + in = DAC output
CM1CON1bits.C1NCH = 0b00; // - in = C12IN0- pin
// -> C1OUT = 1 if C12IN0- > DAC (2.97 V)

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 16


© Gooligum Electronics 2014 www.gooligum.com.au

Thus, the comparator 1 output bit (C1OUT) will be set to ‘1’ whenever the input on C12IN0- is higher than
the upper threshold (generated by the DAC) of 2.97 V – indicating an error condition.
The second comparator is configured in much the same way, but using the FVR as the positive input:
// configure comparator 2
CM2CON0bits.C2ON = 1; // comparator enabled
CM2CON0bits.C2OE = 0; // external output disabled
CM2CON0bits.C2POL = 0; // output not inverted
CM2CON0bits.C2SP = 0; // low-power mode
CM2CON0bits.C2HYS = 1; // hysteresis enabled
CM2CON0bits.C2SYNC = 0; // asynchronous output
CM2CON1bits.C2PCH = 0b10; // + in = fixed voltage ref
CM2CON1bits.C2NCH = 0b00; // - in = C12IN0- pin
// -> C2OUT = 1 if C12IN0- < FVR (2.05 V)

This comparator is configured with its output bit (C2OUT) set to ‘1’ whenever the input on C12IN0- is less
than the lower threshold (generated by the FVR) of 2.05 V – again indicating an error condition.
Note that, as we saw in lesson 8, we can select whether each comparator responds (by setting its interrupt
flag, waking the PIC from sleep mode) to rising and/or falling edges on its output bit.
Since we want the PIC to wake when an error condition occurs, the interrupt flag should be set, waking the
device from sleep, when the comparator output goes high (indicating an error condition) – so we need to
enable rising edge detection on each comparator. On the other hand, we don’t want the PIC to wake when
the input returns to the allowed range (quite the opposite – the PIC enter sleep mode then), so we should not
enable falling edge detection on either comparator.
So, to enable the comparator interrupts, we have:
// enable comparator interrupts (for wake on change)
INTCONbits.PEIE = 1; // enable peripheral interrupts
// enable comparator 1 interrupt:
CM1CON1bits.C1INTP = 1; // enable rising edge detection
CM1CON1bits.C1INTN = 0; // disable falling edge detection
// (wake only on rising output transitions)
// ( -> input rose above upper threshold)
PIE2bits.C1IE = 1; // set enable bit
// enable comparator 2 interrupt:
CM2CON1bits.C2INTP = 1; // enable rising edge detection
CM2CON1bits.C2INTN = 0; // disable falling edge detection
// (wake only on rising output transitions)
// ( -> input fell below lower threshold)
PIE2bits.C2IE = 1; // set enable bit

Once again, because the comparator module is a peripheral, the peripheral interrupt enable bit, PEIE, must
be set in addition to the comparator interrupt enable bits. And, because we’re not actually using interrupts in
this example, we leave the global interrupt enable bit, GIE, clear.

In the example in lesson 8, the LED was turned on for one second, whenever the comparator input fell below
the threshold. But that doesn’t seem appropriate when we’re using two LEDs to indicate when the input is
too high or too low.
So, in this example, the “Low” LED stays on as long as the input is lower than the low threshold (as detected
by comparator 2):
// test for and wait while input low
while (CM2CON0bits.C2OUT) // while input < low threshold (2.05 V)
{
LO = 1; // turn on "low" LED
}

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 17


© Gooligum Electronics 2014 www.gooligum.com.au

Similarly, the “High” LED stays on as long as the input is higher than the high threshold (as detected by
comparator 1):
// test for and wait while input high
while (CM1CON0bits.C1OUT) // while input > high threshold (2.97 V)
{
HI = 1; // turn on "high" LED
}

After getting through these two polling loops, we can be sure that the input is neither too low nor too high, so
we can turn off the LEDs (which may have been turned on in the polling loops):
// input within acceptable range at this point, so turn off LEDs
LATC = 0;

Or you may prefer to refer to turn off each LED individually, with:
// input within acceptable range at this point, so turn off LEDs
LO = 0;
HI = 0;

And since the input is now within the allowed range, we can enter sleep mode (clearing the interrupt flags
first, as usual):
// go into standby (low power) mode
PIR2bits.C1IF = 0; // clear comparator interrupt flags
PIR2bits.C2IF = 0;
SLEEP(); // enter sleep mode

When the PIC comes out of sleep mode, one of the comparator outputs must have changed, meaning that the
input has crossed one of the thresholds, so we loop back and test the comparator outputs again.

Complete program
Here’s how it all fits together:
/************************************************************************
* Description: Lesson 9, example 4 *
* *
* Demonstrates wake-up on dual comparator change *
* using fixed voltage reference and DAC as comparator thesholds *
* *
* Turns on Low LED when C12IN0- < 2.0 V (low light level) *
* or High LED when C12IN0- > 3.0 V (high light level) *
* until input returns to middle (2.0 - 3.0 V) range *
* then sleeps until the next change *
* *
*************************************************************************
* *
* Pin assignments: *
* C12IN0- = voltage to be measured (e.g. pot output or LDR) *
* RC3 = "Low" LED *
* RC1 = "High" LED *
* *
************************************************************************/

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 18


© Gooligum Electronics 2014 www.gooligum.com.au

#include <xc.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

// Pin assignments
#define HI LATCbits.LATC1 // "High" LED
#define LO LATCbits.LATC3 // "Low" LED

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


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

// configure ports
TRISC = 0b110101; // configure RC1 and RC3 as outputs
// (RA1/C12IN0- is an input)
ANSELAbits.ANSA1 = 1; // select analog mode for RA1/C12IN0-

// configure fixed voltage reference


FVRCONbits.FVREN = 1; // FVR enabled
FVRCONbits.CDAFVR = 0b10; // Comp+DAC output is 2x (= 2.048 V)
// -> output 2.048 V to comparators

// configure DAC
DACCON0bits.DACEN = 1; // DAC enabled
DACCON0bits.DACOE = 0; // DACOUT pin disabled
DACCON0bits.DACPSS = 0b00; // +ve source is Vdd
DACCON0bits.DACNSS = 0; // -ve source is Vss
DACCON1bits.DACR = 19; // DAC out = 19/32*Vdd
// = 2.97 V (if Vdd = 5.0 V)

// configure comparator 1
CM1CON0bits.C1ON = 1; // comparator enabled
CM1CON0bits.C1OE = 0; // external output disabled
CM1CON0bits.C1POL = 1; // output inverted
CM1CON0bits.C1SP = 0; // low-power mode
CM1CON0bits.C1HYS = 1; // hysteresis enabled
CM1CON0bits.C1SYNC = 0; // asynchronous output
CM1CON1bits.C1PCH = 0b01; // + in = DAC output
CM1CON1bits.C1NCH = 0b00; // - in = C12IN0- pin
// -> C1OUT = 1 if C12IN0- > DAC (2.97 V)

// configure comparator 2
CM2CON0bits.C2ON = 1; // comparator enabled
CM2CON0bits.C2OE = 0; // external output disabled
CM2CON0bits.C2POL = 0; // output not inverted
CM2CON0bits.C2SP = 0; // low-power mode
CM2CON0bits.C2HYS = 1; // hysteresis enabled
CM2CON0bits.C2SYNC = 0; // asynchronous output

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 19


© Gooligum Electronics 2014 www.gooligum.com.au

CM2CON1bits.C2PCH = 0b10; // + in = fixed voltage ref


CM2CON1bits.C2NCH = 0b00; // - in = C12IN0- pin
// -> C2OUT = 1 if C12IN0- < FVR (2.05 V)

// enable comparator interrupts (for wake on change)


INTCONbits.PEIE = 1; // enable peripheral interrupts
// enable comparator 1 interrupt:
CM1CON1bits.C1INTP = 1; // enable rising edge detection
CM1CON1bits.C1INTN = 0; // disable falling edge detection
// (wake only on rising output transitions)
// ( -> input rose above upper threshold)
PIE2bits.C1IE = 1; // set enable bit
// enable comparator 2 interrupt:
CM2CON1bits.C2INTP = 1; // enable rising edge detection
CM2CON1bits.C2INTN = 0; // disable falling edge detection
// (wake only on rising output transitions)
// ( -> input fell below lower threshold)
PIE2bits.C2IE = 1; // set enable bit

/*** Main loop ***/


for (;;)
{
// test for and wait while input low
while (CM2CON0bits.C2OUT) // while input < low threshold (2.05 V)
{
LO = 1; // turn on "low" LED
}

// test for and wait while input high


while (CM1CON0bits.C1OUT) // while input > high threshold (2.97 V)
{
HI = 1; // turn on "high" LED
}

// input within acceptable range at this point, so turn off LEDs


LATC = 0;

// go into standby (low power) mode


PIR2bits.C1IF = 0; // clear comparator interrupt flags
PIR2bits.C2IF = 0;
SLEEP(); // enter sleep mode
}
}

Conclusion
We’ve seen in this lesson that the fixed voltage reference and digital-to-analog converter (DAC) can be used
as comparator input thresholds, reducing circuit complexity and adding flexibility, such as the ability to use
the DAC to test an input against multiple threshold levels.
We also saw that the DAC output can be made available externally, adding an analog-output capability to
what we have seen so far as purely a digital-output device.

We’ve learned a lot by flashing LEDs, but to go much further we’ll need to be able to display output in
numerical form, so the next lesson introduces 7-segment displays, allowing us to explore the topics of look-
up tables and display multiplexing.

Enhanced Mid-Range PIC C, Lesson 9: Voltage Reference and DAC Page 20

You might also like