Professional Documents
Culture Documents
Osmeoisis 2022-09-06 15-33-19PIC - Enh - C - 9
Osmeoisis 2022-09-06 15-33-19PIC - Enh - C - 9
au
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”).
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.
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
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.
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.
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.
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.
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>
// Pin assignments
#define LED LATCbits.LATC1 // indicator LED
// 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 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
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.
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.
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.
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.
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.
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.
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 *
* *
*************************************************************************
* *
* Pin assignments: *
* DACOUT (RA0) = sawtooth output *
* *
************************************************************************/
#include <xc.h>
// 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
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.
Here is an oscilloscope plot of the output on the DACOUT pin7, with the 1 nF capacitor removed from the
circuit:
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.
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!
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).
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.
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
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>
// Pin assignments
#define HI LATCbits.LATC1 // "High" LED
#define LO LATCbits.LATC3 // "Low" LED
// 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
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.
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
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)
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
}
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 *
* *
************************************************************************/
#include <xc.h>
// Pin assignments
#define HI LATCbits.LATC1 // "High" LED
#define LO LATCbits.LATC3 // "Low" LED
// 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 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
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.