; File: top.asm
; Author: Gaspar Sinai <gaspar@yudit.org>
; Version: 2010-04-27
; Copyright: Gaspar Sinai <gaspar@yudit.org>
; Top module for frequency counter
;
; gotchas:
;
; 1. PIC16F877A PortA5 is open collector. Needs a 3.9k pull-up resistor.
; 2. The display was two bright when driven with 25 mA / 5 = 5 mA.
;    Change resistor from 180 Ohm to 270 Ohm. 17 mA  / 5 = 3.3 mA
;       240mW
;    Change resistor from 180 Ohm to 330 Ohm. 14 mA  / 5 = 2.7 mA
;       200mW
;    (*) Change resistor from 180 Ohm to 510 Ohm. 8 mA  / 5 = 1.6 mA
;       115mW
; 510 Ohm is used because the blue led was very bright even with 330 Ohms.
; With 180 Ohms it was burning bright.
; RD1,2 is pulled up to 5V by 3.9K. There is a switch to ground for
; calibration (+/- 1 HHz steps of reference frequency.)

#include "common.inc"
#include "util.inc"
#include "i2c.inc"

    ; XT_OSC external osciallator XT,HS
    ; WDT_OFF no watchdog timer
    ; WRT_OFF no prog memmory write protection
    ; CP_OFF no code protect
    ; CPD_OFF no code protect data
    ; BODEN_ON brown out reset
    ; PWRTE_OFF power up timer disabled
    ; LVP_OFF low voltage in-circuit serial programming disabled

    __CONFIG (_XT_OSC & _WDT_ON & _WRT_OFF & _CP_OFF & _CPD_OFF & _BODEN_ON & _PWRTE_OFF & _LVP_OFF)


CounterI2CAddress   equ H'21'  ; The address of the counter CPLD 

; We use double scan, but in fact a single scan will suffice 
; 10 works for a single scan 10 digit display.
; 
DisplayDarkness     equ .10    ; 5-20 - 5 is lightest.



    udata_shr           ; bank0 and 1.

SaveW           res 1   ; Irq4 saves W here
IntVarA         res 1   ; Used by interrupt
IntCounter      res 1   ; Interrupt Counter

    udata

SaveStatus      res 1   ; Irq4 Saves STATUS here
SavePCLATH      res 1   ; Irq4 Saves PCLATH here
SaveFSR         res 1   ; Irq4 Saves FSR here
Display         res 9   ; 9 7+1 segments abcdef. LSB first. 9th digit is portD
Led             res 1   ; 1 LED at bit 0

BCDBuffer       res 5   ; Packed BCD result buffer.
DecimalPoint    res 1   ; Nibble position of decimal poing in BCDBuffer.
VarI            res 1   ; general Purpose variable
VarJ            res 1   ; general Purpose variable
VarK            res 1   ; general Purpose variable
VarBuffer       res .10 ; general purpose buffer
Mux             res 1   ; Display Multipexer
PortDState      res 1   ; Port D last state.
CalibrationData res 1   ; Calibration add/substract

    org     0
    goto    Main

    org     4
;
; interrupt service    
;
   ; Save registers

    movwf   SaveW
    swapf   STATUS,W
    clrf    STATUS      ; Point to bank 0 and clear all the flags
    movwf   SaveStatus

    ; We have no paging, but I left it here for completness.
    ; You can save 4 cycles if you are in a pinch by commenting out
    ; the save PCLATH lines.

    movf    PCLATH,W
    movwf   SavePCLATH

    movf    FSR,W
    movwf   SaveFSR


    ; Interrupt handling
    banksel PIR1

    btfsc   PIR1,TMR2IF
    goto    TimerInterrupt

ReturnFromInterrupt:

    clrf    STATUS      ; Point to bank 0 and clear all the flags

    ; Restore registers

    movf    SaveFSR,W
    movwf   FSR

    movf    SavePCLATH,W
    movwf   PCLATH

    swapf   SaveStatus,W
    movwf   STATUS
    swapf   SaveW,F
    swapf   SaveW,W

    retfie

TimerInterrupt:

    clrwdt
    
    clrf    STATUS       ; Point to bank 0 and clear all the flags

    BANKSEL PORTC
    ; PorrC[0,1,2,5] PortD[0] is digit select 0
    movlw   b'00000000'  ; All digits off
    movwf   PORTC

    BANKSEL PORTD
    movlw   b'00000000'  ; All digits off
    movwf   PORTD

    ; Read Display[4-8]

    BANKSEL Mux          ; Next multiplexer index
    incf    IntCounter,F
    incf    Mux,W

    addlw   -DisplayDarkness  ; valid values: 0-4. To make it darker we have 5.
    btfsc   STATUS,C    ; skip borrow
    movlw   -DisplayDarkness
    addlw   DisplayDarkness
    movwf   Mux

    addlw   Display+4
    movwf   FSR
    comf    INDF,W      ; 0 is on

    ; PortB[0-7]
    BANKSEL PORTB
    movwf   PORTB

    ; Read Display[0-4]

    BANKSEL Mux
    movf    Mux,W

    addlw   Display
    movwf   FSR
    comf    INDF,W      ; 0 is on

    ; PortA[0-5]
    BANKSEL PORTA
    movwf   PORTA

    ; PortC[6-7]
    andlw   b'11000000'
    movwf   IntVarA     ; Available in all banks - no need for it now.

    ; PorrC[0,1,2,5] is digit select 0

    ; TODO, FIXME send digit selector 1 active.
    BANKSEL Mux
    movf    Mux,W
    addlw   0
    btfsc   STATUS,Z
    bsf     IntVarA,0
    addlw   -1
    btfsc   STATUS,Z
    bsf     IntVarA,1
    addlw   -1
    btfsc   STATUS,Z
    bsf     IntVarA,2
    addlw   -1
    btfsc   STATUS,Z
    bsf     IntVarA,5

    addlw   -1
    btfsc   STATUS,Z
    goto    IntPortD

IntPortDBack:

    movf    IntVarA,W
    BANKSEL PORTC
    movwf   PORTC

    BANKSEL PIR1
    bcf     PIR1,TMR2IF
    goto    ReturnFromInterrupt

IntPortD:

    BANKSEL PORTD
    movlw   1
    movwf   PORTD

    goto    IntPortDBack

Main:
    ;
    ; Program starts here
    ;
    clrf    STATUS              ; Select bank 0 and clear all flags.
    clrf    Mux                 ; Display Multipexer
    bsf     OPTION_REG,NOT_RBPU ; Disable B pull-ups

    ; Min 7 max 33 ms WDT without prescaler

    bcf     OPTION_REG,PS0      ; Prescaler WDT 0
    bcf     OPTION_REG,PS1      ; Prescaler WDT 0
    bcf     OPTION_REG,PS2      ; Prescaler WDT 0
    bsf     OPTION_REG,PSA      ; WDT Source
    bcf     OPTION_REG,T0CS     ; Dont use T0CK1, we have this as port.

    ;
    ; Set up port directions
    ; PortA[0-5]PortC[67] is 8 segments of Display0-3 (abcdef.) 0=a
    ; PortB[0-8] is 8 segments of Display4-8 (abcdef.) 0=a
    ; PortC 4 bit [012][5] PortD[0] Digit Selector
    ; PortC-3 I2C SCL - set as input 
    ; PortC-4 I2C SDA - set as input
    ;
    BANKSEL TRISA 
    movlw   b'00000000' ; 0 output 
    movwf   TRISA
    BANKSEL PORTA 
    movlw   b'11111111' ; off
    movwf   PORTA

    BANKSEL TRISB 
    movlw   b'00000000' ; 0 output 
    movwf   TRISB
    BANKSEL PORTB 
    movlw   b'11111111' ; off
    movwf   PORTB

    BANKSEL TRISC 
    movlw   b'00011000' ; 0 output 
    movwf   TRISC
    BANKSEL PORTC 
    movlw   b'00000000' ; digits off
    movwf   PORTC

    BANKSEL TRISD 
    movlw   b'00000110' ; 0 output 1,2 is a switch to ground.
    movwf   TRISD
    BANKSEL PORTD 
    movlw   b'00000000' ; 9th digit off
    movwf   PORTD

    ; Initialize Display interrupt
    BANKSEL T2CON
    clrf    T2CON           ; Clear prescaler  
    bsf     T2CON,TMR2ON    ; Enable timer2
    bsf     T2CON,T2CKPS0
    bcf     T2CON,T2CKPS1   ; 1:4 prescaler 4 MHz clock -> 250kHz

    BANKSEL PR2
    movlw   .124             ; Divide by 125 for 2kHz
    movwf   PR2

    ; Note: we dont enable SSPIE for I2C
    BANKSEL PIE1
    clrf    PIE1
    bsf     PIE1,TMR2IE     ; Enable the TMR2 to PR2 match interrupt

    BANKSEL PIR1
    bcf     PIR1,TMR2IF     ; Clear timer2 interrupt flag

    BANKSEL INTCON
    bsf     INTCON,PEIE     ; Peripheral Interrupt Enable
    bsf     INTCON,GIE      ; Global Interrupt Enable

    call    SayHello

    ; Timer interrupt for display multiplexing

    ; I2C
    clrf    STATUS              ; Select bank 0 and clear all flags.
    movlw   .100
    call    WaitWms

MainLoopTmp:
    clrf    STATUS              ; Select bank 0 and clear all flags.

    ; FIXME
    ; goto    MainLoopTmp
    ; SGC
    movlw   .1              ; Synchronize to display
    call    WaitWms

    call    I2CInit

    movlw   CounterI2CAddress
    movwf   I2CAddress 


MeasureLoop:
    call    ClearCalibrationData

    movlw   b'01000000'         ;  Stop measuring
    movwf   I2CData 

    movlw   .1              ; Synchronize to display
    call    WaitWms

    call    I2CWrite
    movlw   .50
    call    WaitWms

MainWaitReady:

    BANKSEL PORTD
    movf    PORTD,W
    andlw   b'00000110'
    call    ReadCalibrationData
    

    movlw   b'11000000'         ;  Start measurement, Read status
    movwf   I2CData 

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    
    iorlw   0
    btfss   STATUS,Z
    goto    DeviceNotFound

    btfss   I2CData,0           ; Skip ready measure    
    goto    MainWaitReady

    btfsc   I2CData,1           ; Skip no underflow    
    goto    MainUnderflow

    ;
    ; When there is a measurement made, we can update 
    ; the calibration data if user pressed the calibration switches.
    ;
    call    ReadCalibrationData
    iorlw   0
    btfss   STATUS,Z
    call    TimeBaseAdd         ; Modify our timebase with calibration data

    ;
    ; Put the reference frequency to multiplicand
    ;
    movlw   0
    call    TimeBaseRead
    BANKSEL AritBuffer
    movwf   AritBuffer+0

    movlw   1
    call    TimeBaseRead
    BANKSEL AritBuffer
    movwf   AritBuffer+1

    movlw   2
    call    TimeBaseRead
    BANKSEL AritBuffer
    movwf   AritBuffer+2

    movlw   3
    call    TimeBaseRead
    BANKSEL AritBuffer
    movwf   AritBuffer+3

    ; Read the measured cycles, calculate frequency

    ; 0..3 cnt_x. Keep measure bit 1.

    movlw   b'10000000'
    iorlw   0
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+8

    movlw   b'10000000'
    iorlw   1
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+9

    movlw   b'10000000'
    iorlw   2
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+.10

    movlw   b'10000000'
    iorlw   3
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+.11

    call    Multiply

    ; 4..7 cnt_r

    movlw   b'10000000'
    iorlw   4
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+8

    movlw   b'10000000'
    iorlw   5
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+9

    movlw   b'10000000'
    iorlw   6
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+.10

    movlw   b'10000000'
    iorlw   7
    movwf   I2CData

    movlw   .1              ; Synchronize to display
    call    WaitWms
    call    I2CWriteRead
    movf    I2CData,W
    movwf   AritBuffer+.11

    call    CalculateFrequency
    call    BCDBufferToDisplay

    movlw   .2
    call    WaitWms

    goto    MeasureLoop

DeviceNotFound:

    call    I2CError
    goto    MeasureLoop


MainUnderflow:


    clrf    Led
    movlw   b'10111111'               ; 0.
    movwf   Display+8
    movlw   b'00111111'               ; 0
    movwf   Display+7
    movlw   b'00111111'               ; 0
    movwf   Display+6
    movlw   b'00111111'               ; 0
    movwf   Display+5
    movlw   b'00111111'               ; 0
    movwf   Display+4
    movlw   b'00111111'               ; 0
    movwf   Display+3
    movlw   b'00111111'               ; 0
    movwf   Display+2
    movlw   b'00111111'               ; 0
    movwf   Display+1
    movlw   b'00111111'               ; 0
    movwf   Display+0

    movlw   .100
    call    WaitWms

    goto    MeasureLoop


CalculateFrequency:

    ; Call divide and calculate 
    ; BCDBuffer and DecimalPoint 

    call    Divide
    call    ToBCDInteger

    ; Copy DecNumber -> VarBuffer
    movf    DecNumber+0,W
    movwf   VarBuffer+0
    movf    DecNumber+1,W
    movwf   VarBuffer+1
    movf    DecNumber+2,W
    movwf   VarBuffer+2
    movf    DecNumber+3,W
    movwf   VarBuffer+3
    movf    DecNumber+4,W
    movwf   VarBuffer+4
    movf    DecNumber+5,W
    movwf   VarBuffer+5
    movf    DecNumber+6,W
    movwf   VarBuffer+6
    movf    DecNumber+7,W
    movwf   VarBuffer+7
    movf    DecNumber+8,W
    movwf   VarBuffer+8
    movf    DecNumber+9,W
    movwf   VarBuffer+9

    call    DivideFraction
    call    ToBCDFraction

    clrf    BCDBuffer
    clrf    BCDBuffer+1
    clrf    BCDBuffer+2
    clrf    BCDBuffer+3
    clrf    BCDBuffer+4

    ; Shift DecNumber (10 bytes = 20 nibbles) into BCDBuffer (4 bytes = 8 nibbles)
    movlw   -.20    ; Initial decimal point nibble position.
    movwf   DecimalPoint

    movlw   .20+8    ; max shift left. Leading zero kept for units.
    movwf   VarI

MainIntShiftLoop1:

    movlw   4       ; 4 bit in a nibble
    movwf   VarJ

MainIntShiftLoop2:

    bcf     STATUS,C
    rlf     DecNumber+0,F
    rlf     DecNumber+1,F
    rlf     DecNumber+2,F
    rlf     DecNumber+3,F
    rlf     DecNumber+4,F
    rlf     DecNumber+5,F
    rlf     DecNumber+6,F
    rlf     DecNumber+7,F
    rlf     DecNumber+8,F
    rlf     DecNumber+9,F

    rlf     VarBuffer+0,F
    rlf     VarBuffer+1,F
    rlf     VarBuffer+2,F
    rlf     VarBuffer+3,F
    rlf     VarBuffer+4,F
    rlf     VarBuffer+5,F
    rlf     VarBuffer+6,F
    rlf     VarBuffer+7,F
    rlf     VarBuffer+8,F
    rlf     VarBuffer+9,F

    rlf     BCDBuffer+0,F
    rlf     BCDBuffer+1,F
    rlf     BCDBuffer+2,F
    rlf     BCDBuffer+3,F
    rlf     BCDBuffer+4,F

    decfsz  VarJ,F
    goto    MainIntShiftLoop2

    incf    DecimalPoint,F
   
    ; Check if there is any nonzero most significant digit. 
    movf    BCDBuffer+4,W
    andlw   0x0f
    btfss   STATUS,Z
    goto    MainIntShiftEnd

    decfsz  VarI,F
    goto    MainIntShiftLoop1

MainIntShiftEnd:

    return

BCDBufferToDisplay:
    ; 
    ; Display what is in BCDBuffer
    ; A Decimal point will be displayed at DecimalPoint
    ;
    movf    BCDBuffer+0,W
    call    Get7SegmentPattern
    movwf   Display+0
    movf    DecimalPoint,W
    btfsc   STATUS,Z
    bsf     Display+0,7
    movwf   VarI

    swapf   BCDBuffer+0,W
    call    Get7SegmentPattern
    movwf   Display+1
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+1,7

    movf    BCDBuffer+1,W
    call    Get7SegmentPattern
    movwf   Display+2
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+2,7

    swapf   BCDBuffer+1,W
    call    Get7SegmentPattern
    movwf   Display+3
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+3,7

    movf    BCDBuffer+2,W
    call    Get7SegmentPattern
    movwf   Display+4
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+4,7

    swapf   BCDBuffer+2,W
    call    Get7SegmentPattern
    movwf   Display+5
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+5,7

    movf    BCDBuffer+3,W
    call    Get7SegmentPattern
    movwf   Display+6
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+6,7

    swapf   BCDBuffer+3,W
    call    Get7SegmentPattern
    movwf   Display+7
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+7,7

    movf    BCDBuffer+4,W
    call    Get7SegmentPattern
    movwf   Display+8
    decf    VarI,F
    btfsc   STATUS,Z
    bsf     Display+8,7

    incf    DecimalPoint,W
    btfss   STATUS,Z
    return

    ; MHz.KHz.Hz on 8 digits. We have 9 digits now.
    ; bsf     Display+2,7
    ; bsf     Display+5,7

    return

Get7SegmentPattern
    andlw   h'0f'
    movwf   VarJ

    movlw   high(PatternTable)
    movwf   PCLATH
    movf    VarJ,W

    addlw   low (PatternTable)
    btfsc   STATUS,C
    incf    PCLATH,F
    movwf   PCL

PatternTable:
    retlw   b'00111111'   ; 0
    retlw   b'00000110'   ; 1
    retlw   b'01011011'   ; 2
    retlw   b'01001111'   ; 3
    retlw   b'01100110'   ; 4
    retlw   b'01101101'   ; 5
    retlw   b'01111101'   ; 6
    retlw   b'00000111'   ; 7
    retlw   b'01111111'   ; 8
    retlw   b'01101111'   ; 9
    retlw   b'01110111'   ; A
    retlw   b'01111100'   ; b
    retlw   b'00111001'   ; C
    retlw   b'01011110'   ; d
    retlw   b'01111001'   ; E
    retlw   b'01110001'   ; F
    
I2CError:

    clrf    Led
    movlw   b'01110001'         ; F
    movwf   Display+8
    movlw   b'00000100'         ; i
    movwf   Display+7
    movlw   b'01011011'         ; 2
    movwf   Display+6
    movlw   b'01011000'         ; c
    movwf   Display+5
    movlw   b'01111001'         ; E
    movwf   Display+4
    movlw   b'01010000'         ; r
    movwf   Display+3
    movlw   b'01010000'         ; r
    movwf   Display+2
    movlw   b'01011100'         ; o
    movwf   Display+1
    movlw   b'01010000'         ; r
    movwf   Display+0

    return

SayHello:

    clrf    Led
    movlw   b'11101101'               ; S.
    movwf   Display+8
    movlw   b'01101110'               ; Y
    movwf   Display+7
    movlw   b'00111110'               ; U
    movwf   Display+6
    movlw   b'01110011'               ; P
    movwf   Display+5
    movlw   b'00111110'               ; U
    movwf   Display+4
    movlw   b'01000000'               ; -
    movwf   Display+3
    movlw   b'01110001'               ; F
    movwf   Display+2
    movlw   b'10000110'               ; 1.
    movwf   Display+1
    movlw   b'00111111'               ; 0
    movwf   Display+0

    return

WaitWms:

    ; Wait W ms. It is waiting W millisedonds if interrupt is every
    ; millisecond. Currenly it waits only 500us as interrupt is 2KHz now. 
    BANKSEL IntCounter

WaitWmsLoop:

    addlw   0
    btfsc   STATUS,Z
    return 
    addlw   -1

    btfsc   IntCounter,0
    goto    WaitWms1

WaitWms0:
    btfss   IntCounter,0
    goto    WaitWms0
    goto    WaitWmsLoop

WaitWms1:
    btfsc   IntCounter,0
    goto    WaitWms1
    goto    WaitWmsLoop

ClearCalibrationData:
    ;
    ; Clear calibration data
    ;
    BANKSEL PORTD
    movf    PORTD,W
    andlw   b'00000110'

    clrf    STATUS              ; Select bank 0 and clear all flags.
    movwf   PortDState
    clrf    CalibrationData

ReadCalibrationData:
    ;
    ; Update calibration data and read it into W
    ;
    BANKSEL PORTD
    btfss   PORTD,1             ; 0 active - count up
    goto    ReadCalibration1
    btfss   PORTD,2             ; 0 active - count down
    goto    ReadCalibration2
    clrf    STATUS              ; Select bank 0 and clear all flags.
    movlw   b'00000110'
    movwf   PortDState
    movf    CalibrationData,W
    return  

ReadCalibration1:
    ; Add 1 to CalibrationData if PortDState,1 is set
    BANKSEL PortDState
    btfsc   PortDState,1
    incf    CalibrationData,F
    bcf     PortDState,1
    movf    CalibrationData,W
    return 
    

ReadCalibration2:
    ; Substract 1 from CalibrationData if PortDState,2 is set
    BANKSEL PortDState
    btfsc   PortDState,2
    decf    CalibrationData,F
    bcf     PortDState,2
    movf    CalibrationData,W
    return 

    end
