;===== FreqM.asm =====
;Frequency counter and volt meter using 16F1705
;to be used by low distortion signal generator
;alternative to 16F1705 is 16F18323
;display= 2 lines x 16 characters LCD panel
;spec: freq=1Hz-1MHz (-1.5V~+1.5V sine wave input),
; volt=0.10-10.00Vrms (full-wave rectified AC),
; update=1sec+α, Vcc=3.3V, clk=4MHz internal HF
;coding start 7/20/2020
;Copyright 2020 T. Fujiwara all rights reserved
;version 1.0 release, 8/3/2020
;v.1.1 8/11: T1 overflow handling change
;****** Code must be in UTF-8 format ********
;pin assignment:
;1 Vdd |*------| ;14 Vss
;2 RA5 T1 clock-in | | ;13 RA0 Program
;3 RA4 Analog Voltage-in | | ;12 RA1 Program
;4 RA3 Reset | | ;11 RA2 T0 clock-in, 32.768kHz
;5 RC5 RS | | ;10 RC0 D4
;6 RC4 E | | ;9 RC1 D5
;7 RC3 D7 |_______| ;8 RC2 D6
;MPLAB X set: case=insensitive, radix=dec, Device=16F1705,
; tool=PICkit 4, compiler=mpasm
LIST P=16F1705, c=132, n=0
#include "p16f1705.inc"
; CONFIG1
;__config1 0xC7E4
__CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_ON & _CP_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
; CONFIG2
; __config2 0xFEFF
__CONFIG _CONFIG2, _WRT_OFF & _PPS1WAY_ON & _ZCDDIS_ON & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_ON & _STVREN_ON
radix dec
;--- GPR addresses 20~6F ----
dispchr equ 0x20 ;32bytes of display characters 20~3F
binfreq equ 0x40 ;3 bytes of binary frequency is saved here 40~42
binfreq0 equ 0x43 ;T1 overflow reg 43
binvolt equ 0x44 ;3 bytes of binary voltage is saved here 44~46
flag equ 0x47 ;T0 overflow flag bit0, 1=O/F 47
loopcnt equ 0x48 ;various loop count 48
loopcnt2 equ 0x49 ;ditto 49
work1 equ 0x4A ;3 bytes of packed hex to be converted to dec4A~4c
work2 equ 0x4d ;3 bytes of working value to subtract 4d~4f
work3 equ 0x50 ;7 bytes of accumulater, unpacked decimal 50~56
;unused: 57,58,59,5a
work5 equ 0x5b ;misc work 5b
work6 equ 0x5c ;misc 5c
loopcnt3 equ 0x5d ;mainloop cnt low 5d
loopcnt4 equ 0x5e ;mainloop cnt high 5e
lcdwk1 equ 0x5f ;lcd work area 5f
lcdwk2 equ 0x60 ; ditto 60
;up to 6F
common equ 0x70 ;common 16 registers 70~7F
;--- Literal Values ---
calibrate equ .37 ;T0 deviation and "late T1 halt" is calibrated
;00= no comp
;01= +1 ppmb (displays higher freq than measured)
;7F= +127 ppmb (+121ppm)
;FF= -1 ppmb (makes lower freq display)
;80= -128 ppmb (-122ppm)
;ppmb is "binary ppm": 1/1MiB, 1ppmb= 0.9527559ppm
;note: 32.768kHz crystal has +-50ppm deviation
adcoffseth equ 0xff ;ADC offset compensation, use it for LSB error
adcoffsetl equ 0xff ;0xffff makes measured 1 to display 0.00Vrms
;--- bits ----
w equ 0
f equ 1
rs equ b'00100000' ;rs bit to LCD, used as "iorlw rs"
e equ 4 ;write strobe to LCD, used as "bsf portc,e"
;--- Code ---
;RESET START ===============
org 0
nop
goto entry
db 0x11 ;version
nop
org 4 ;interrupt routine
;INT routine ==============
bcf INTCON,GIE ;kill all interrupts
;check T0 int
btfss INTCON,TMR0IF ;1sec overflowed? skip if yes
goto t1up ;otherwise, it must be the T1 overflow
;move T1 counter to binfreq
bcf T1CON,TMR1ON ;stop T1 counting, 8us late, +8ppmb err
movf TMR1L,w ;save T1 counters
movwf binfreq+2
movf TMR1H,w
movwf binfreq+1
movf binfreq0,w
movwf binfreq
;clear counters and restart
bcf INTCON,TMR0IF ;reset T0 int flag
clrf TMR1L
clrf TMR1H
clrf binfreq0
clrf TMR0 ;reset TMR0 and prescaler, need two insn
bsf T1CON,TMR1ON ;resume T1 counting
;******** start T0/T1 freq. count ******
bsf flag,0 ;notice 1 sec int happened
goto iret
t1up: ;increment timer 1 overflow reg
incf binfreq0,f ;increment at every 64k cycles
bcf PIR1,TMR1IF ;reset o/f bit in pir1
iret: bsf INTCON,GIE ;enable global int
retfie
;Entry ==============
entry: ;-----------------------initialize PIC and LCD
call init
;-----------------------display LCD, welcome msg
call lcd1
call lcd2
;-----------------------enable interrupts of T0/1
movlb 1
clrf PIE3
clrf PIE2 ;disable misc int's
clrf PIE1
bsf PIE1,TMR1IE ;enable T1 int *****
movlb 0
clrf INTCON
bsf INTCON,TMR0IE ;enable T0 int
bsf INTCON,PEIE ;enable periferal int's (for T1)
;-----------------------clear counters ;T0=0, T1=0
clrf TMR1L ;ensure tmr1h will have no change by tmr1l o/f
clrf TMR1H
clrf TMR0 ;clear T0/prescaler, takes place 2 cycles later
clrf TMR1L ;start T0 and T1 simultaneously
bsf INTCON,GIE ;enable global int
;Main Loop =======================================================
mainloop:
;get voltage and prepare displaychr, display line2
call voltage
;0.1sec wait loop while watching flag0 (1 sec timer comp)
movlw 78 ;1281us*78=0.1sec
movwf loopcnt4
clrf loopcnt3
loop8: ;wait 0.1sec loop, avoid too frequent voltage display
btfss flag,0 ;test if freq measured
goto sklp8 ;skip if no 1sec process
call frequency ;treat frequency number, display line1
goto mainloop ;then skip wait
sklp8: decfsz loopcnt3,f
goto loop8 ;one loop=5insn=5us, 256loops+1=1281us
decfsz loopcnt4,f
goto loop8
goto mainloop
;Subroutines =====================================================
frequency: ;get binfreq, convert to dec, display on LCD
bcf flag,0 ;reset timer0 o/f flag, we display frequency
;move to work1
movf binfreq,w
movwf work1
movf binfreq+1,w
movwf work1+1
movf binfreq+2,w
movwf work1+2
;calibrate: make work1 higher or lowerer by calibration value
movlw calibrate ;ppmb value
movwf work5 ;test if no calibration
sublw 0x00
btfsc STATUS,Z
goto gofreq ;branch if not required
call clearwork2
btfsc work5,7 ;is it negative?
goto negative
;positive calibration
call mullp ;multiply by ppmb times
addwf work1+2,f ;add w to freq
clrw
addwfc work1+1,f
addwfc work1,f
goto gofreq
negative: ;negative calibration
comf work5,f ;complement: FF=>00, 80=>7F
movlw 1 ;make it 2's complement
addwf work5,f ;00=>1, 7F=80
call mullp
subwf work1+2,f ;sub from freq
clrw
subwfb work1+1,f
subwfb work1,f
goto gofreq
mullp: movf work1+1,w
addwf work2+2,f ;accumulate high two bytes into work2 three bytes
movf work1,w
addwfc work2+1,f
clrw
addwfc work2,f
decfsz work5,f ;loop ppmb times
goto mullp ;exit if multiply comp
swapf work2+1,w ;work2 has comp value x16
andlw 0x0f
movwf work6 ;high nibble of work2+1 is now low nibble of work6
swapf work2,w
andlw 0xf0
addwf work6,f ;fill low nibble of work2 into high nibble
movf work6,w ;w has comp value
return
gofreq:
call h2d ;work1=packed hex => work6=ascii decimal
;move from work3 to dispchr, erase left 0's, put comma
movlw work3+6
movwf FSR0L
clrw
movwf FSR0H ;fsr points work3+6, lowest byte
moviw FSR0--
movwf dispchr+10 ;1
moviw FSR0--
movwf dispchr+9 ;10
moviw FSR0--
movwf dispchr+8 ;100
movlw a','
movwf dispchr+7
moviw FSR0--
movwf dispchr+6 ;1k
moviw FSR0--
movwf dispchr+5 ;10k
moviw FSR0--
movwf dispchr+4 ;100k
movlw a','
movwf dispchr+3
moviw FSR0--
movwf dispchr+2 ;1M
;erase left 0's
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+2
movwf dispchr+3 ;erase comma
movf dispchr+4,w
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+4
movf dispchr+5,w
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+5
movf dispchr+6,w
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+6
movwf dispchr+7 ;comma location
movf dispchr+8,w
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+8
movf dispchr+9,w
sublw 0x30
btfss STATUS,Z
goto skip3
movlw a' '
movwf dispchr+9
skip3: ;no more left zeros
call lcd1 ;display frequency
return
voltage: ;get voltage and store in binvolt =========
movlb 1
bsf ADCON0,1 ;AD Go! bit1
btfsc ADCON0,1
goto $-1 ;wait for adc complete
;read V
movf ADRESL,w
movlb 0
movwf binvolt+2
movlb 1
movf ADRESH,w
movlb 0
movwf binvolt+1
;move to work1
call clearworks
movf binvolt+2,w
movwf work1+2
movf binvolt+1,w
movwf work1+1
;error compensation
movlw 0
subwf work1+2,w ;test if low byte is zero
btfss STATUS,Z
goto adadjust ;compensate if not zero
movlw 0
subwf work1+1,w ;test high byte
btfsc STATUS,Z
goto noadjust
adadjust:
movlw adcoffsetl ;ADC offset compensation
addwf work1+2,f
movlw adcoffseth
addwfc work1+1,f
noadjust:
;convert hex to ascii decimal
call h2d ;convert hex in work1 to ascii decimal in work3
;move ascii decimal in work3 to dispchr
movlw work3+6
movwf FSR0L
clrw
movwf FSR0H ;fsr points work3+6, lowest byte
moviw FSR0--
movwf dispchr+26
moviw FSR0--
movwf dispchr+25
moviw FSR0--
movwf dispchr+23
moviw FSR0--
movwf dispchr+22
;erase left 0
sublw 0x30
btfss STATUS,Z
goto skip2
movlw a' '
movwf dispchr+22
skip2: ;if binvolt=1023(0x3ff) then display ">", else erase ">"
movlw a' '
movwf dispchr+21
movf binvolt+2,w
sublw 0xff
btfss STATUS,Z ;lower 8bit is FFh
goto skip1 ;no, keep ">" erased
;test high byte
movf binvolt+1,w
sublw 0x03
btfss STATUS,Z ;flag z if full scale
goto skip1
movlw a'>'
movwf dispchr+21
skip1: call lcd2 ;display volt
return
;clear work1,2,3 ==========
clearworks:
clrf work1
clrf work1+1
clrf work1+2
clearwork23:
clrf work3
clrf work3+1
clrf work3+2
clrf work3+3
clrf work3+4
clrf work3+5
clrf work3+6
clearwork2:
clrf work2
clrf work2+1
clrf work2+2
return
;Wait Loop routine ============
wait: movwf loopcnt ;approx. 769 * w +3 us, i.e. w:52=40ms
clrf loopcnt2
loop1: decfsz loopcnt2,f
goto loop1 ;one loop=3insn=3us, 256loops+1=769us
decfsz loopcnt,f
goto loop1
return
;LCD Display routine ==============
lcd1: ;display the first line
movlw dispchr
movwf FSR0L ;set gpr address 0x20, in FSR0
movlw 0x08 ;set address 0
call sendhalf
clrw
call sendhalf2
goto send16bytes
lcd2: ;display the second line
movlw dispchr+16
movwf FSR0L ;set gpr address 0x30, in FSR0
movlw 0x0a ;set address 0x28
call sendhalf
movlw 0x08
call sendhalf2
send16bytes:
clrw
movwf FSR0H
movlw 16 ;16 characters to send
movwf lcdwk2 ;loop count=16
loop3: moviw FSR0++ ;get a byte from dispchr
movwf lcdwk1 ;save in work
swapf lcdwk1,w ;get high nibble
andlw 0x0f
call sendhalfd ;send high nibble to lcd
movf lcdwk1,w ;get low nibble
andlw 0x0f
call sendhalf2d ;send low nibble to lcd
decfsz lcdwk2,f ;repeat it 16 times
goto loop3
return
sendhalfd: ;send higher nibble for data
iorlw rs ;flag this is data
sendhalf: ;send w reg to lcd, high nibble
movwf PORTC
bsf PORTC,e ;up e
bcf PORTC,e ;down e, write comp
return
sendhalf2d: ;send lower nibble for data
iorlw rs
sendhalf2: ;send w reg to lcd, 2nd time, low nibble
movwf PORTC
bsf PORTC,e ;up e
bcf PORTC,e ;down e, write comp
wait56: movlw 18
movwf loopcnt2 ;wait for 53us
loop2: decfsz loopcnt2,f
goto loop2
return
;Hex to Decimal routine ===========
h2d: ;source is in work1, in packed hex format, 3 bytes
;work2 is 3 bytes working area of packed hex value of a decimal digit
;work3 is accumulator, filled by unpacked decimal digit
digit7: call clearwork23 ;7th digit 1,000,000d=0F4240h
movlw 0x0f
movwf work2
movlw 0x42
movwf work2+1
movlw 0x40
movwf work2+2
d7lp: call compare ;w1>w2? yes, then sub
btfss STATUS,C ;yes, skip and sub
goto digit6 ;no or Z, go next digit
call subw1w2
incf work3,f ;inc w3 1000000's digit
goto d7lp
digit6: call clearwork2 ;6th digit 100,000d=0186A0h
movlw 0x01
movwf work2
movlw 0x86
movwf work2+1
movlw 0xa0
movwf work2+2
d6lp: call compare
btfss STATUS,C
goto digit5
call subw1w2
incf work3+1,f
goto d6lp
digit5: call clearwork2 ;5th digit 10,000d=2710h
movlw 0x27
movwf work2+1
movlw 0x10
movwf work2+2
d5lp: call compare
btfss STATUS,C
goto digit4
call subw1w2
incf work3+2,f
goto d5lp
digit4: call clearwork2 ;4th digit 1,000d=03E8h
movlw 0x03
movwf work2+1
movlw 0xe8
movwf work2+2
d4lp: call compare
btfss STATUS,C
goto digit3
call subw1w2
incf work3+3,f
goto d4lp
digit3: call clearwork2 ;3rd digit 100d=64h
movlw 0x64
movwf work2+2
d3lp: call compare
btfss STATUS,C
goto digit2
call subw1w2
incf work3+4,f
goto d3lp
digit2: call clearwork2 ;2nd digit 10d=0Ah
movlw 0x0a
movwf work2+2
d2lp: call compare
btfss STATUS,C
goto digit1
call subw1w2
incf work3+5,f
goto d2lp
digit1: movf work1+2,w ;residue 1d=01h
movwf work3+6
;ascii
movlw 0x30
iorwf work3+6,f
iorwf work3+5,f
iorwf work3+4,f
iorwf work3+3,f
iorwf work3+2,f
iorwf work3+1,f
iorwf work3,f
return
compare: ;compare work1 and work2, C=0 if no sub, work2>work1
movf work2,w
subwf work1,w ;test 1st byte
btfss STATUS,Z ;skip if equal
return
movf work2+1,w
subwf work1+1,w
btfss STATUS,Z
return
movf work2+2,w
subwf work1+2,w
return ;return having Z or C
subw1w2: ;do (work1 - work2 => work1) one time only
movf work2+2,w ;lowest byte first
subwf work1+2,f
movf work2+1,w
subwfb work1+1,f
movf work2,w
subwfb work1,f
return
;PIC and LCD Initialize routine==========================
init: ;initialize PIC
;-------------------Interrupt
;kill interrupt
clrf INTCON
;-------------------Clock
;INTOSC= internal high frequency, 4MHz, by HFINTOSC
movlb 1 ;select bank 1
movlw b'01101000' ;4x pll disabled, 4MHz, internal HF clock
movwf OSCCON
;-------------------disbale OP Amp
movlb 10
bcf OPA1CON,7
bcf OPA2CON,7
;-------------------port A/C set
movlb 0
clrf PORTA ;clear PORTA
clrf PORTC ;clear PORTC
clrf PIR1 ;clear T1 o/f bit
clrf PIR2 ;clear flag bits etc
clrf PIR3 ;clear flag bits
movlb 1
movlw b'00110111' ;port A bits are all input
movwf TRISA
clrf TRISC ;all pins of port C are output
movlb 2
clrf LATA ;clear PORTA data LATCH
clrf LATC ;ditto PORTC
movlb 3
movlw b'00010000' ;RA4=analog for ADC
movwf ANSELA
clrf ANSELC
;--------------------T1CON
movlb 0
movlw b'10000101' ;source=external, prescaler off,
;disable secondary osc, T1 enabled
movwf T1CON
movlw b'00000000' ;timer1 gating disabled, count when gate=low,
;timer0 O/F gate killed
movwf T1GCON
;--------------------T0 option
movlb 1
movlw b'00110110' ;weak pullup off, T0=pin, prescaler=on and 128,
;rising edge
movwf OPTION_REG
;--------------------WPU latch exclude RA5, RA4, RA2
movlb 4
movlw b'00001011' ;wpu for RA3, RA1,RA0
movwf WPUA
clrf WPUC
;--------------------wdt off
movlb 1
clrf WDTCON ;off wdt
;------------------- ADC set
movlb 2
clrf FVRCON ;do not use FVR(2.048V)
movlb 1
movlw b'00001101' ;ADC=AN3(RA4),
;ADC enabled (at least 9us before AD-GO)
movwf ADCON0
movlw b'11000000' ;right justified, Fosc/4, Vref-=Vss, Vref+=Vdd
;ADC Clock must be 1us~4us
movwf ADCON1
clrf ADCON2 ;no auto conversion triger
movlb 0 ;bank 0
;-------------------initialize data
;F=x,xxx,xxx Hz
;V= xx.xx Vrms
movlw a'F'
movwf dispchr
movlw a'='
movwf dispchr+1
movlw a'1'
movwf dispchr+2
movlw a','
movwf dispchr+3
movlw a'2'
movwf dispchr+4
movlw a'3'
movwf dispchr+5
movlw a'4'
movwf dispchr+6
movlw a','
movwf dispchr+7
movlw a'5'
movwf dispchr+8
movlw a'6'
movwf dispchr+9
movlw a'7'
movwf dispchr+10
movlw a' '
movwf dispchr+11
movlw a'H'
movwf dispchr+12
movlw a'z'
movwf dispchr+13
movlw a' '
movwf dispchr+14
movlw a' '
movwf dispchr+15
movlw a'V'
movwf dispchr+16
movlw a'='
movwf dispchr+17
movlw a' '
movwf dispchr+18
movlw a' '
movwf dispchr+19
movlw a' '
movwf dispchr+20
movlw a' '
movwf dispchr+21
movlw a'1'
movwf dispchr+22
movlw a'2'
movwf dispchr+23
movlw a'.'
movwf dispchr+24
movlw a'3'
movwf dispchr+25
movlw a'4'
movwf dispchr+26
movlw a' '
movwf dispchr+27
movlw a'V'
movwf dispchr+28
movlw a'r'
movwf dispchr+29
movlw a'm'
movwf dispchr+30
movlw a's'
movwf dispchr+31
;-----------------------initialize LCD
movlw 52
call wait ;40ms wait
movlw b'00000011' ;-----------
call sendhalf
movlw 6
call wait ;wait 4.1ms
movlw b'00000011' ;-----------
call sendhalf
movlw 1
call wait ;wait100us
movlw b'00000011' ;-----------
call sendhalf2
movlw b'00000010' ;-----------
call sendhalf2
;4bit enabled
movlw b'00000010' ;----------- fuction set
call sendhalf
movlw b'00001100' ;4bit mode, 2 lines, 5x10 dots
call sendhalf2
clrw ;----------- display on/off
call sendhalf
movlw b'00001100' ;display on, cursor off, no blink
call sendhalf2
clrw ;----------- clear display, takes 2.2ms
call sendhalf
movlw b'00000001' ;clear
call sendhalf2
movlw 3
call wait ;wait 2.2ms, clear comp
clrw ;----------- entry mode
call sendhalf
movlw b'000110' ;increment direction, no screen shift
call sendhalf2
return
testend: bsf PORTC,e
goto $ ;debug purpose
end