; 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