ADC Voltage Measurement

This schematic shows the addition of a 10k resistor (R2) and a 1uF capacitor (C12). This is just a very crude integrator that will suffice for testing our ADC programming capabilities. The input to the integrator is our newly created PWM output (CCP2) on pin 12. The junction of R2/C12 is fed to pin 2 which is analog-to-digital converter channel 0 (AN0).

ACD Schematic

ADC Software Configuration

The configuration registers we are concerned with when setting up the A/D converter are ADCON0, ADCON1, TRISA.

ADCON0[7:6] selects the conversion clock frequency. According to the “Mid-Range MCU Family” datasheet, since we are running at 16MHz, a value of b’10’ is required to set the AD clock source of 2uS. Channel select bits are set to b’000′ to select AN0 as the active analog input. THe GO/DONE bit is b’0′, and ADON can be set to b’1′ to enable the ADC system. All of this yields a configuration byte of 0x81.

ADCON1[7] (ADFM) controls the justification of our 10-bit A/D result in the ADRESH and ADRESL registers. This code requires “left justification”, so this bit is set to b’0′. PCFG[3:0] selects the analog/digital configuration of PORT A. We only require AN0 to be an analog input. Also, we want to use the internal 5V as the reference to the ADC. We can easily get away with that because the input is being derived from the PIC itself. There is no chance of exceeding the 5V/GND limits with the R/C integrator we’re using. So that gives us a value of b’1110′ for the port configuration and a final value of 0x0e for this register.

TRISA is set to 0x3f since there are no outputs to drive on this port.

ADC Acquisition

A conversion is started by setting the GO/DONE bit in ADCON0. We are just going to poll the GO/DONE bit to determine when the acquisition is complete (GO/DONE goes low). After the resulting value is calculated and displayed, the GO/DONE bit is set again to start another acquisition. These measurements are asynchronous to the changing input on AN0 which can be a source of error. But for our purposes, this will be “good enough”.

ADC Data Display

(This description uses the DL1414 as the display device. See the relevant code in the download package for specific LCD code.) In the main loop at mloop:, we just test the GO/DONE bit in ADCON0. When it is clear, we fall into the display routine at adc_svc:. The first thing to do is to get the ADC data out of ADRESL/H. I disable interrupts while I change to bank 1. This is probably not required, but I’m paranoid. The values are placed into 3 registers so that they may be processed. The ADC data is 10 bits long: 8 MSBs are in ARHimg (also a copy in Meter) and 2 bits are in ARLimg[7:6]. Now, how to display this value?

We will make the assumption that these 10 bits represent a range of 0 to 5 volts. This will be in error depending on your power supply voltage and offsets internal to the PIC. But for our purposes here, we will assume this 5V range is accurate. We can divide the 1024 possible values by 5 giving a result (divisor) of 204. 204 represents 1 volt. Since we are limited to 8-bit math on the PIC, it would be more convenient to scale our operations to 8 bits. This can be done if we reduce both the result and the divisor to 8 bits. Dividing each by 4 we get 1024/4 = 256 and 204/4 = 51. So to calculate whole volts, we can divide ARHing (ignoring the 2 LSB in ARLimg is identical to a 2-bit right shift, or a divide by 4) by 51.

439
adc_svc:
440
; READ/DISPLAY ADC RESULTS:
441
bcf
INTCON, 7
; disable interrupts
442
nop
443
bsf
STATUS, 5
; Bank1 < save result registers:/div>
444
movf
ADRESL, w
; ADCL result…
445
bcf
STATUS, 5
; Bank0
446
bsf
INTCON, 7
; enable interrupts
447
movwf
ARLimg
; …to storage
448
movf
ADRESH, w
; ACLH result…
449
movwf
ARHimg
; …to storage
450
movwf
Meter
; …and to Meter
451
clrf
Cdigit
452
454
movlw
D’51’
; represents 1V on ADC
455
subwf
Meter, f
456
btfsc
STATUS,,Z
457
goto
ones_zero
458
btfss
STATUS, C
459
goto
ones_store
460
incf
Cdigit, f
; count a volt
461
goto
ones
462
ones_store:
463
movlw
D’51’
; represents 1V on ADC
464
addwf
Meter, f
; restore Meter to before CARRY
465
goto
ones_comp
466
ones_zero:
467
incf
Cdigit, f
468
ones_comp:
469
movlw
0x30
470
iorwf
Cdigit, w
; ascii char in W
471
movwf
Char0
; display volts
472
movlw
“.”
473
movwf
Char1
474
475
; NORMALIZE ADC result into ARHimg:
476
movf
Meter, w
477
movwf
ARHimg
; update msb image
478
rlf
ARLimg, f
; bit 1 of ADC LSB result to C…
479
rlf
ARHimg, f
; …and then into ADC MSB
480
rlf
ARLimg, f
; bit 0 of ADC LSB result to C…
481
rlf
ARHimg, f
; …and then into ADC MSB
482
movf
ARHimg, w
483
movwf
Meter
; restore Meter
484
485
clrf
Cdigit
486
tens_cnt:
487
movlw
D’21’
; tenth of a volt on ADC
488
subwf
Meter, f
489
btfsc
STATUS, Z
490
goto
tens_zero
491
btfss
STATUS, C
493
incf
Cdigit, f
494
goto
tens_cnt
495
tens_store:
496
movlw
D’21’
; tenth of a volt on ADC
497
addwf
Meter, f
; restore Meter to before CARRY
498
goto
tens_comp
499
tens_zero:
500
incf
Cdigit, f
501
tens_comp:
502
movlw
0x30
503
iorwf
Cdigit, w
; ascii char in W
504
movwf
Char2
; display tenths of volts
505
506
bcf
STATUS, C
507
rrf
Meter, f
; since 2 is the divisor for hundredth, rrf Meter
508
movlw
D’10’
; test for ’10’
509
subwf
Meter, w
;
510
btfsc
STATUS, Z
; skif Meter != 10
511
decf
Meter, f
512
movf
Meter, w
513
514
iorlw
0x30
515
movwf
Char3
; display hundreths of volts
516
call
disp_chars
517
call
delay
518
call
delay
519
call
delay
520
call
delay
521
bsf
ADCON0, GO
; start an acquisition
522
bsf
PORTA, 1
; DEBUG – ACQUISITION
523
goto
mloop

Because the PIC has no divide instruction, we will use the technique of subtracting the divisor, 51, from from ARHimg, and counting (in Cdigit) each subtract until a borrow occurs. This all happens on lines 454 throught line 460. Note that on the 16F876, when the subtract instruction causes a ‘borrow’, the C (carry) bit is cleared. This is backward from a carry generated by addition. When we zero the Meter register (line 456) or generate the ‘borrow’ (line 458), we can store the result for the ‘ones’ value. In the case when the Z bit is set, the value in Meter is correct and we jump to ‘ones_zero’. But in the case when the ‘borrow’ was generated, we have subtracted once too many times and we jump to ‘ones_store’ which adds back the value of 51 to Meter. Note that Cdigit holds the correct voltage value for either of these two paths taken.

Now we make Cdigit an ASCII value on line 470. We know that its value can only be 0 through 5, so it’s safe to OR it with the value of 0x30. Then it is written into the display buffer at Char0. The next display character is always the decimal point so we store that at Char1.

So now we have the remainder in Meter, and the two LSBs from the ADC are sitll in ARLimg. We want to bring all of these bits into a single register. After our ‘division’, the largest value that can possibly be in Meter is decimal 50, or 0x32. So we see that bits 7 and 6 of Meter will be ’00’. This gives us room to shift-in the 2 LSBs. This happens on lines 476 through 483.

To calculate tenths of a volt, use the same successive subtraction with the value of 21 as ‘divisor’. The tenths result is stored in Char2.

The hundredths value should be the reaminder. But because the value of 21 we used does not evenly divide all possible values, we may be left with a value greater than 9. I’m taking the easy way out and am just ‘clipping’ the hundredth value to a maximum of 9. This results in some error but it’s close enough for our purposes right now. This value is then stored in Char3, we call the display routine, and start another ADC conversion by setting the GO/DONE bit in ADCON0.




©copyright 2014 pretzeLogic LLC. All rights reserved.
No part of this page may be reproduced without permission.
Software, schematics, and text are presented as reference works only.
No claim as to useability, suitability, or correctness for any application is made.