16F628 PIC A/D using JAL

The 16F628 pic doesn’t actually have a A/D converter. It has a couple of comparators and a software controllable voltage reference which does pretty well for most applications. I’m looking at using it for a low-battery cutoff device to protect 12V lead-acid batteries.The two comparators would work for that – one for the low-water mark, and one set a little bit above for a level the charge needs to reach before the load is reapplied, with some time delay probably too.

However, I came across Microchip’s Application note AN700 Make a Delta-Sigma Converter Using a Microcontroller’s Analog Comparator Module which shows how to use the comparator as an A/D  converter. There are plenty of PICs that already have an A/D converter – the 16F88 has several analogue channels, but the 16F628 is cheaper. A lot of applications need just one analogue value sampled, and my application also wants an A/D that includes 12V. If I used a chip with an onboard A/D I’m going to need two resistors anyway to pad 12V down to the 5V range, and AN700 fig 7 shows how to take advantage of the external parts to shift the A/D range up to about 12V.

Trouble is, I want to use JAL, to make life easier when I write the rest of the code, and the assembler mode of JAL didn’t work. I swiped this from here probably derived from here but it didn’t work for me. I took a look at the code and suspected the assembler part, because what was happening was as soon as I called the DeltaSigA2D() the whole program seemed to sit down and start again from the top, resulting in endless printing of “STARTED” and little else.

Thinking about what JAL is probably doing with assembler, I suspected some of the clever space-saving constructs like the bit in eat5cycles that goes

goto +1

which was supposed to be

goto $+1

could be asking for trouble unless I could really assume that next instruction is the next one in the program space. In the end if you’re going to use something like JAL you aren’t trying to eke out every last drop of code space, so I figured I would nut the cleverness and use stacked nops. And it worked.

 

-- JAL 2.4i
-- Just the A/D 15 June 2013
-- modded by Richard
--
-- adc_test_16f628_v03
-- after DeltaSig.asm v1.00 from Dan Butler, Microchip Technology Inc. 02 December 1998
-- after http://den-nekonoko.blog.so-net.ne.jp/2010-11-25 and AN700
-- continually spews 10-bit value on TXD at 9600 baud, 5V=1024 0V=0
-- AN700 Fig 2
include 16f628_inc_all
-- We'll use internal oscillator. It work @ 4MHz
pragma target clock       4_000_000
const word _fuses         = 0x3fff ; default value
pragma target osc         INTRC_NOCLKOUT
pragma target WDT    off
pragma target MCLRE  off
pragma target PWRTE  on
pragma target BODEN    on
pragma target LVP    off
-- HS, -BOD, ,-LVP, -WDT, -CP  = 0x3F22
; pragma target fuses       0x3D02
--enable_digital_io()

; const bit enhanced_flash_eeprom = false
include pic_general
include delay

var word result
var byte result_l
var byte result_h
var byte counter1
var byte counter2

const byte booted[] = "STARTED"

pin_a0_direction = input
pin_a1_direction = input
pin_a2_direction = input ; comparator in / vref out
pin_a3_direction = output ; comp1 out
pin_a4_direction = output ; comp2 out (OD)
pin_b2_direction=output ; RS232 TX

const bit usart_hw_serial = TRUE
const serial_hw_baudrate = 9600
include serial_hardware
serial_hw_init()

include print
include format

Procedure InitDeltaSigA2D() is
VRCON=0b11101100 --    0xEC
CMCON=0b00000110   -- 0x06 two common ref comps with outputs
end procedure

;
; Delta Sigma A2D
; The below contains a lot of nops and goto next instruction. These
; are necessary to ensure each pass through the Elop1 takes the same
; amount of , no the path through the code.
;
Procedure DeltaSigA2D() is
assembler
         LOCAL    elop1,comphigh,complow,eat2cycles,endelop1,eat5cycles,exit1
--DeltaSigA2D
        clrf counter1
        clrf counter2
        clrf result_l
        clrf result_h
        movlw 0x03 ; set up for 2 analog comparators with common reference
        movwf cmcon
elop1:
        btfsc c1out ; is comparator high or low?
--        btfsc cmcon,c1out ; is comparator high or low?
        goto complow ; go the low route
comphigh:
        nop ; necessary to timing even
        bcf porta,3 ; porta.3 = 0
        incfsz result_l,f ; bump counter
        goto eat2cycles ;
        incf result_h,f ;
        goto endelop1 ;
complow:
        bsf porta,3 ; comparator is low
        nop ; necessary to keep timing even
        goto eat2cycles ; same here
eat2cycles:
        goto endelop1 ; eat 2 more cycles
endelop1:
        incfsz counter1,f ; count this lap through the elop1.
        goto eat5cycles ;
        incf counter2,f ;
        movf counter2,w ;
        andlw 0x04 ; are we done? (we're done when bit2 of
--        btfsc status,_z ; the high order byte overflows to 1).
        btfsc Z --status,z ; the high order byte overflows to 1).
        goto elop1 ;
        goto exit1
eat5cycles:
        nop  -- this really seems to hate the goto $+1, keeps resetting. I am not so short of code space, case to be made to turn everything into nops
        nop
        --goto +1 ; more wasted time to keep the elop1s even
        nop ;
        goto elop1 ;
exit1:
        movlw 0x06 ; set up for 2 analog comparators with common reference
        movwf cmcon
end assembler
end procedure

function rescale(WORD IN value) RETURN WORD is
-- 1024 corresponds to 5V
-- multiply by 50000/1024 (~49)
var word temp =value*49
temp=temp/100 -- now lose excess precision
return temp

end function

InitDeltaSigA2D()

print_string(serial_hw_data,booted)

forever loop
DeltaSigA2D()
result=word(result_h)*256 + word(result_l) -- you must explicity make result_h a word as described on p11 of JAL manual
result=rescale(result)
format_word_dec(serial_hw_data, result, 3, 2)
serial_hw_data = " "
serial_hw_data = 13 -- CR
serial_hw_data = 10 -- LF
delay_1ms(500)   -- this is purely to allow the terminal display to catch up and not overload buffer of the slow device
end loop

Files

adc_test_16f628_v03.jal

and the hex file

adc_test_16f628_v03.hex

There’s a case to be made for using a voltage reference and setting it to be some convenient power of 2 like 2.05 V to save all the messy integer calculation in rescale

This is a video of the display on the serial port compared with a multimeter on the input, connected to a 5k pot between 0V and +5V. I should have moved the control a bit slower to allow the 0.5s sampling to catch up at times, but it shows it is reasonably accurate.