; DEVICE = 16F628A
; Mains grid frequency indicator, 12 LEDs indicate the line frequency in 20 mHz increments from 49.90 to 50.10 Hz
; US 60 Hz version should be trivial
; Filename: netzlupe.asm
; v0.0 170723 Start project
; v0.1 170724 Skip display counter to smoothen readout
; v0.2 170724 Add moving average (N=4)
; v1.0 170724 Final release
; v1.1 170724 Moved lamp test so display is up to date after it has run
; v1.2 170730 Fixed Lamp Test not run because GIE enabled too early
; v1.3 170901 Backlight
; v1.4 170902 Backlight (second version)
; v1.5 190120 Improved resolution by four (gliding effect)
; v1.6 190120 Moved display routine to main loop to fix flicker
; v1.7 190120 Added LED blanking when entering interrupt to (almost) remove random flickering LEDs
; v1.8 190224 Sanity check input value moving average (20ms)
LIST P=16F628A, F=INHX8M
#include <p16f628a.inc>
__CONFIG _XT_OSC & _WDT_OFF & _LVP_OFF & _CP_OFF
; Equates
RESET_V EQU 0x00 ; Address of RESET Vector
OSC_FREQ EQU D'4000000' ; Oscillator Frequency is 4.0 MHz
; Registers
FLAGS EQU 0x20 ; Only used for lamp test
MAN EQU 0x21 ; Moving Average * N
MA EQU 0x22 ; Moving Average which gets displayed
MAPREV EQU 0x23 ; Previous Moving Average
MAINT EQU 0x24 ; Intermediate value for moving average calculation (SUBWF)
XI EQU 0x25 ; Measurement to be averaged
SKIP EQU 0x26 ; Skip display counter to make readout less jittery
GLIDE EQU 0x27 ; Offset for gliding display
DISPSAV EQU 0x28 ; Saved display value (from MA)
DISP EQU 0x29 ; Gliding display value
COUNTER EQU 0x2A ; Used for backlight
TMRSANI EQU 0x2B ; High byte of Timer1, used for sanity check input
; Flags
#define LAMPTST FLAGS,0
; Ports
#define N6 PORTB,1 ; RED, <49.90 Hz (>20,040 us)
#define N5 PORTB,2 ; RED, 49.90-49.92 Hz (+32 us)
#define N4 PORTB,3 ; YEL, 49.92-49.94 Hz (+24 us)
#define N3 PORTB,4 ; YEL, 49.94-49.96 Hz (+16 us)
#define N2 PORTB,5 ; GRN, 49.96-49.98 Hz (+8 us)
#define N1 PORTB,6 ; GRN, 49.98-50.00 Hz (0 us)
#define P1 PORTB,7 ; GRN, 50.00-50.02 Hz (-8 us)
#define P2 PORTA,0 ; GRN, 50.02-50.04 Hz (-16 us)
#define P3 PORTA,1 ; YEL, 50.04-50.06 Hz (-24 us)
#define P4 PORTA,2 ; YEL, 50.06-50.08 Hz (-32 us)
#define P5 PORTA,3 ; RED, 50.08-50.10 Hz (-40 us)
#define P6 PORTA,4 ; RED, >50.00 Hz (< -40 us)
; Port RB0 is INT for 50 Hz input
ORG 0x00
GOTO START
ORG 0x04
; Interrupt handlers
BCF INTCON,INTF ; Clear the interrupt caused by RB0 INT (there are no others)
GOTO UPDATE
RETFIE ; Just in case
LAMPTEST
BTFSS N6
GOTO LTN5
BTFSS N5
GOTO LTN4
BTFSS N4
GOTO LTN3
BTFSS N3
GOTO LTN2
BTFSS N2
GOTO LTN1
BTFSS N1
GOTO LTP1
BTFSS P1
GOTO LTP2
BTFSS P2
GOTO LTP3
BTFSS P3
GOTO LTP4
BTFSS P4
GOTO LTP5
BTFSS P5
GOTO LTP6
BSF P6
BCF LAMPTST
RETFIE
LTN5
BSF N6
BCF N5
RETFIE
LTN4
BSF N5
BCF N4
RETFIE
LTN3
BSF N4
BCF N3
RETFIE
LTN2
BSF N3
BCF N2
RETFIE
LTN1
BSF N2
BCF N1
RETFIE
LTP1
BSF N1
BCF P1
RETFIE
LTP2
BSF P1
BCF P2
RETFIE
LTP3
BSF P2
BCF P3
RETFIE
LTP4
BSF P3
BCF P4
RETFIE
LTP5
BSF P4
BCF P5
RETFIE
LTP6
BSF P5
BCF P6
RETFIE
UPDATE
BTFSC LAMPTST ; Blank displays to avoid glitches during interrupt only when lamp test is done
GOTO MAV
MOVLW 0xFF
MOVWF PORTA
MOVWF PORTB ; Switch off all LEDs
MAV
; Calculate moving average: MA*[i]=MA*[i-1]+X[i]-MA*[i-1]/N (MA*=MA*N, N=4)
CALL Sanitize
BCF STATUS,C ; Clear carry
RRF XI,1 ; divide by two (->0x20)
MOVFW MAN ; Get previous moving average*N
MOVWF MAPREV ; Store previous moving average*N
ADDWF XI,0 ; Add X[i]
MOVWF MAINT ; Intermediate calculation MA
BCF STATUS,C ; Clear carry
RRF MAPREV,1 ; Calculate previous moving average/N
BCF STATUS,C ; Clear carry
RRF MAPREV,0 ; Divide by 4 and store in W
SUBWF MAINT,0 ; and subtract MA
MOVWF MAN ; Store new moving average *N
MOVWF MA ; and store in MA (temp)
BCF STATUS,C ; Clear carry
RRF MA,1 ; Calculate moving average (MA*/N)
BCF STATUS,C ; Clear carry
RRF MA,1 ; Divide by 4 and keep in MA
BTFSC LAMPTST ; Test for lamp test here to ensure proper value to display when lamp test is done
GOTO LAMPTEST
DISPLAY
; Backlight
MOVLW 0xFF
MOVWF PORTA
MOVWF PORTB
BCF N6
CALL DELAYBL
BSF N6
BCF N5
CALL DELAYBL
BSF N5
BCF N4
CALL DELAYBL
BSF N4
BCF N3
CALL DELAYBL
BSF N3
BCF N2
CALL DELAYBL
BSF N2
BCF N1
CALL DELAYBL
BSF N1
BCF P1
CALL DELAYBL
BSF P1
BCF P2
CALL DELAYBL
BSF P2
BCF P3
CALL DELAYBL
BSF P3
BCF P4
CALL DELAYBL
BSF P4
BCF P5
CALL DELAYBL
BSF P5
BCF P6
CALL DELAYBL
BSF P6
; The final value gets decoded and displayed
DECFSZ SKIP
RETFIE
MOVLW 0x19 ; Every 500 ms (25*20ms) update display
MOVWF SKIP
MOVFW MA ; Store saved display value
MOVWF DISPSAV
RETFIE
; End interrupt handlers
START
CLRF STATUS ; Do initialization, Select bank 0
CLRF INTCON ; Clear int-flags, Disable interrupts
CLRF PCLATH ; Keep in lower 2KByte
MOVLW B'00000111' ; Disable comparators (damn you Microchip! Should have been 0x00!)
MOVWF CMCON
BSF STATUS, RP0 ; Select bank 1
CLRF TRISA ; RA7-0 Outputs
CLRF TRISB ; RB7-0 Outputs
BSF TRISB,0 ; Except RB0 (INT)
MOVLW B'11000000' ; Disable weak pullups, INT rising edge
MOVWF OPTION_REG
BCF STATUS, RP0 ; Select bank 0
MOVLW 0xFF
MOVWF PORTA ; Make all PORT A outputs high (LEDs OFF)
MOVWF PORTB ; Make all PORT B outputs high
; Clear/init registers
CLRF FLAGS
MOVLW 0x01 ; To make sure the first readout gets displayed after lamp test
MOVWF SKIP
MOVWF GLIDE
; Set up interrupts
CLRF PIR1 ; Clear peripheral stuff
BSF STATUS,RP0 ; Go to bank 1 (BANKSEL PIE1)
CLRF PIE1 ; Clear more peripheral stuff
BCF STATUS,RP0 ; Go to bank 0
BSF INTCON,INTE ; Enable external interrupt on RB0
; Setup Timer1
BCF T1CON,T1CKPS1 ; These set the TIMER1 prescaler. Values:
BCF T1CON,T1CKPS0 ; 00=1:1 01=1:2 10=1:4 11=1:8
BCF T1CON,T1OSCEN ; Turn off the TIMER1 oscillator (we don't need it because we're using the internal oscillator)
BCF T1CON,TMR1CS ; Select the internal oscillator for TIMER1
BSF T1CON,TMR1ON ; Enable TIMER1
CLRF TMR1H
CLRF TMR1L
BSF LAMPTST
BCF N6 ; Switch on LED N6
BSF INTCON,GIE ; Enable global interrupts
MAIN
BTFSC LAMPTST ; Wait for lamp test to complete
GOTO MAIN
MOVLW 0xFF
MOVWF PORTA
MOVWF PORTB ; Switch off all LEDs
DECFSZ GLIDE
GOTO Show
MOVLW 0x04
MOVWF GLIDE
Show ; Finally show the averaged and offset value
MOVFW DISPSAV ; Get saved display value
MOVWF DISP ; Store gliding diplay value
MOVFW GLIDE ; Get offset
ADDWF DISP,1 ; Add offset to DISP
MOVLW 0xFF
MOVWF PORTA
MOVWF PORTB ; Switch off all LEDs
; Test for P6, <0x0E
MOVFW DISP
SUBLW 0x0E
BTFSC STATUS,C ; less than 0x0C, goto P6
GOTO ONP6
; Test for P5, <0x12
MOVFW DISP
SUBLW 0x12
BTFSC STATUS,C
GOTO ONP5
; Test for P4, <0x16
MOVFW DISP
SUBLW 0x16
BTFSC STATUS,C
GOTO ONP4
; Test for P3, <0x1A
MOVFW DISP
SUBLW 0x1A
BTFSC STATUS,C
GOTO ONP3
; Test for P2, <0x1E
MOVFW DISP
SUBLW 0x1E
BTFSC STATUS,C
GOTO ONP2
; Test for P1, <0x22
MOVFW DISP
SUBLW 0x22
BTFSC STATUS,C
GOTO ONP1
; Test for N1, <0x26
MOVFW DISP
SUBLW 0x26
BTFSC STATUS,C
GOTO ONN1
; Test for N2, <0x2A
MOVFW DISP
SUBLW 0x2A
BTFSC STATUS,C
GOTO ONN2
; Test for N3, <0x2E
MOVFW DISP
SUBLW 0x2E
BTFSC STATUS,C
GOTO ONN3
; Test for N4, <0x32
MOVFW DISP
SUBLW 0x32
BTFSC STATUS,C
GOTO ONN4
; Test for N5, <0x36
MOVFW DISP
SUBLW 0x36
BTFSC STATUS,C
GOTO ONN5
; No test for N6 necessary, >=0x36
BCF N6
CALL DELAYSHOW
GOTO MAIN
ONN5
BCF N5
CALL DELAYSHOW
GOTO MAIN
ONN4
BCF N4
CALL DELAYSHOW
GOTO MAIN
ONN3
BCF N3
CALL DELAYSHOW
GOTO MAIN
ONN2
BCF N2
CALL DELAYSHOW
GOTO MAIN
ONN1
BCF N1
CALL DELAYSHOW
GOTO MAIN
ONP1
BCF P1
CALL DELAYSHOW
GOTO MAIN
ONP2
BCF P2
CALL DELAYSHOW
GOTO MAIN
ONP3
BCF P3
CALL DELAYSHOW
GOTO MAIN
ONP4
BCF P4
CALL DELAYSHOW
GOTO MAIN
ONP5
BCF P5
CALL DELAYSHOW
GOTO MAIN
ONP6
BCF P6
CALL DELAYSHOW
GOTO MAIN
; Subroutine: backlight delay loop
DELAYBL
MOVLW 0x20
MOVWF COUNTER
Backloop
DECFSZ COUNTER
GOTO Backloop
RETURN
; Subroutine: display delay loop
DELAYSHOW
MOVLW 0x50
MOVWF COUNTER
Showloop
DECFSZ COUNTER
GOTO Showloop
RETURN
; Subroutine: sanitize timer value
Sanitize
MOVFW TMR1L ; Get timer1 LSB, 0x4E20 is 20,000 us
ADDLW 0x28 ; Offset and correction -> 0x40
MOVWF XI ; Store into measurement register
MOVFW TMR1H
MOVWF TMRSANI
CLRF TMR1L ; Need to clear Timer1 as soon as possible
CLRF TMR1H
MOVFW XI
SUBLW 0x7F
BTFSS STATUS,C ; If set value of XI is invalid (FF or something)
GOTO CheckLByte
CLRF XI ; XI->0
RETURN
CheckLByte
MOVFW XI
SUBLW 0x7F
BTFSC STATUS,C ; If clear XI->0x7F
RETURN
MOVLW 0x7F
MOVWF XI
RETURN
END