Program Zscan100D
'
'
Device = 18F2420 
Clock = 10          
'
Config
    OSC = HS,       
    LVP = OFF,
    DEBUG = OFF,
    PWRT = ON,
    WDT = OFF,
    PBADEN = OFF,      'makes port B digital
    XINST=OFF '

Include "usart", "EEPROM", "convert", "adc"

Const
    CR =  13,    'carriage return
    LF =  10,    'line feed
    Tab =  9,     'tab feed
    WriteToMem = %0,
    ReadFromMem = %1,
    Step25Hz = %1,
    Step50Hz = %0,
    StoredFreq = %0,
    PotFreq = %1,
    LowStep = 25,       'frequency step in Hz
    HighStep = 50,
    AvgSize = 12,        'number of freq readings to boxcar average

    Vers1 = 2,          'Main version number
    Vers2 = 2,          'sub version number
    
    Gmode = $F,         'Goertzel mode DIP Switch setting = 0
    GplusMode = $7,      'Goertzel plus high/low mode DIP Switch setting = 7
    GPlusBW = 250,       'bandwdith for GPlus mode
    GPlusExclude = 30,    'exclusion bandwdith for GPlus mode 
    GMinusBW = -1*GPlusBW,
    GMinusExclude = -1*GPlusExclude
    
   ' #Define SerialDebug    'if this is defined there are serial outputs for debugging

Dim
    Freq As LongInt,
    AvgAry(AvgSize) As LongInt,  'holds individual frequency readings
    SumFreq As LongInt,
    Pass As Byte,
    AvgPass As Byte,                'for averaging pass
    LEDSel As Integer,
    CenterFreq As Integer,      'center frequency in Hz
    Fixed As PORTC.1,           'if 1 use current pot postion; if 0 use stored memory value  
    SpanWidth As PORTA.5,       'if 1 step width = 25 Hz, if 0 then step width = 50 Hz  
    SaveToMem As PORTC.0,       'if 1 recall from memory; if 0 then write to memory
    InPort As PORTC.5,          'input signal to this port
    Timer As TMR1L.AsWord,
    LEDBank0 As PORTC.2,        'LEDs are connected in 3 banks of 8 each
    LEDBank1 As PORTC.3,
    LEDBank2 As PORTC.4
  
         
'------------------
Sub InitializeADC()
'------------------
// initialise ADC...
   ADC.SetConvTime(FOSC_64)
   ADC.SetAcqTime(5)
   
   'PIC defaults on power up to all as inputs, but still good idea to explicit
   'assign the pins to input
   TRISA.0 = %1    // configure AN0 as an input 
   
   'for 18F2420, follow setup in data sheet, section 19
      VCFG1 = %0  'negative reference to ground
      VCFG0 = %0  'positive reference to Vdd
      PCFG3 = %1            '  The configuration nibble is %1110
      PCFG2 = %1            ' - voltage reference is to Vss
      PCFG1 = %1            ' + voltage reference is to Vdd
      PCFG0 = %0            ' AN0 is analog input, all other PortA pins are DIGITAL 
End Sub


 
'turns LED p on. LEDs are numbered from 0 to 23
'for 0, the center LED is on, - goes left, + goes right
'-------------------------
Sub TurnOn(pID As Integer)
'-------------------------
Const
    PortVal(8) As Byte = (%11111110,%11111101,%11111011,%11110111,%11101111,%11011111,%10111111,%01111111)
Dim
    BankNo As Byte
                                      'to avoid ghosting, follow this order
    Low(LEDBank0)                      'first turn off all LEDs
    Low(LEDBank1)                      'then turn on the one that should be illuminated
    Low(LEDBank2) 
    
    pID = pID + 12
    
    'If pID < 0 Then pID = 0  EndIf    'bound the leds  
    'If pID > 23 Then pID = 23  EndIf  'stick high LED on
    
    If pID < 0 Then GoTo Bailout EndIf    'bound the leds  
    If pID > 23 Then GoTo Bailout EndIf 'stick high LED on
    
    BankNo = pId / 8                  'LEDs connected in 3 banks of 8 each
    PORTB = PortVal(pID Mod 8)        'to illuminate LED, Port B pin taken low to select 1 of 8
    Select BankNo                     'then to select bank, take one of the three PortC pins high
        Case 0    High(LEDBank0)
        Case 1    High(LEDBank1)
        Case 2    High(LEDBank2)
    EndSelect
    Bailout:    
End Sub

'Read DIP switches
'-------------------------
Function ReadDip() As Byte
'-------------------------
   Result = (PORTA And %00011110)      'grab all of port A, mask off parts not wanted
   Result = Result >> 1                'shift right 1 to get 0...$F result, as switches are on A4...A1
End Function

'Initialization code here
'---------------
Sub Initialize()
'---------------
Dim
   I As Integer
   
    TRISB = $00  
    ADCON1 = %00001111
    TRISA = $00
    PORTB = $FF  'turn all off
    PORTA.4 = %1
    PORTA.5 = %1
    Input(InPort)      'has slicer connected to it
    Input(PORTA.1)     'frequency memory selection ports
    Input(PORTA.2)
    Input(PORTA.3)
    Input(PORTA.4)
    Output(LEDBank0)
    Output(LEDBank1)
    Output(LEDBank2)
    
      USART.SetBaudrate(br38400) 
      
    #IfDef SerialDebug
       USART.SetBaudrate(br38400) 
       USART.Write("Good to go and ready to launch!",CR,LF)
       DelayMS(500)
    #EndIf
    
    AvgPass = 0          //set up for averaging
    Pass = 0
    SumFreq = 0
    For I = 0 To AvgSize-1 
        AvgAry(I) = 0
    Next //i
End Sub 


'turns all LEDs off
Sub LEDoff()
'--------------
   Low(LEDBank0)   'turn all LEDs off
   Low(LEDBank1)
   Low(LEDBank2)   
End Sub
   
   

'Measures input frequency
Function GetFreq() As LongInt
'-----------------------------                        
   Repeat Until InPort = %1    'following code ensures that a full half cycle is read
   Repeat Until InPort = %0    'delay one 0 level after the first high level
                               'this ensures a full half cycle is read
   Timer = 0                   'use timer 1 to measure it
   PIE1.0 = 1                  'counter is Fosc/4, so 400 ns/tick
   T1CON.0 = 1                 'start the counter
   
   Repeat                       'now count until the next high level
   Until InPort = %1
                                '
   T1CON.0 = 0                  'stop the timer
   
   #IfDef SerialDebug
    USART.Write(DecToStr(Timer),CR,LF) 
   #EndIf
                                'counter is reading half the interval and the ticks are 
   Result = 1250000 / Timer       '2500000/sec so for half interval, 1250000 is the number to use
                                'timer granularity (400ns) gives ~ 7 Hz error at 3KHz. At more normal frequencies
End Function                    'error is less. At 1 KHz ~ 0.8 Hz error results from +/- 400 ns.

        
'boxcar average routine 
Function ComputeAvg(pFreq As LongInt) As LongInt
'-----------------------------------------------  
Dim
   temp As Byte
   
   temp = AvgPass Mod AvgSize      
   SumFreq = SumFreq + pFreq -AvgAry(temp)
   Result = SumFreq / AvgSize
   AvgAry(temp) = pFreq
   Inc(AvgPass) 
End Function         
    
    
'test LEDs at startup
'----------------------
Sub TestLEDs()
'----------------------
Dim
    I As ShortInt    
   

    For I = -12 To 11
        TurnOn(I)
        DelayMS(150)
    Next
    Low(LEDBank0)   'turn all LEDs off
    Low(LEDBank1)
    Low(LEDBank2)   
    
End Sub

'One LED tone detector mode
Function Goertzel() As Integer
'-----------------------------
Const
    N = 32,             'number of freq readings for Goetrzel algorithm
    Thsld50 = 2*N,      'threshold for 50 Hz bandwidth
    Thsld25 = 3*N       'threshold for 25 Hz bandwidth
    
Dim
   x0,y0,y1,y2,sum As Integer,
   j As Byte,
   I(N) As Byte,               'data read for Goertzel analysis
   MicroSec As Word,
   TempFreq As LongInt,
   Error As LongInt,
   Threshold As Byte           'determines bandwidth
    
   
   x0 = 0
   y0 = 0
   y1 = 0
   y2 = 0
  
   Pass = 0
                             '-800ns
   Repeat                    '2 us for Repeat UNTIL Pass = N  
      I(Pass) = InPort       '6.8 us for assignment
      DelayUS(MicroSec)      ' read 1-bit adc into array     
      Inc(Pass) 
   Until Pass = N
   
   For j = 0 To N-1          'compute Goertzel fcn    
       y0 = I(j) - y2
       y2 = y1
       y1 = y0
   Next
   Sum = y1*y1 + y2*y2     'compute power of signal
   Result = Sum
   
   If SpanWidth = %1 Then Threshold = Thsld25
   Else
      Threshold = Thsld50
   EndIf
   
   If Sum > Threshold Then TurnOn(0)  'apply thresholding
      Else 
         LEDoff
         If ReadDip = GplusMode Then
            TempFreq = ComputeAvg(GetFreq)
            Error = TempFreq - 3* ADC.Read(0)
            'Error = GetFreq - 3* ADC.Read(0)
            
            Select Error
                Case >= GPlusBW                  LEDoff
                Case <= -GPlusBW                 LEDoff
                Case GPlusExclude To GPlusBW    TurnOn(5)
                Case GMinusBW To GMinusExclude  TurnOn(-5)
                                                'USART.Write("*",CR,LF)
                'Case -250 to -30 TurnOn(-5)
                      'USART.Write("*",CR,LF)
            EndSelect 
            'USART.Write(DecToStr(TempFreq),9,DecToStr(Error),CR,LF) 
            'If TempFreq > 3* ADC.Read(0) Then TurnOn(5)  EndIf
            'If TempFreq < 3* ADC.Read(0) Then TurnOn(-5) EndIf
         EndIf     'where want to show high/low
   EndIf
   
   MicroSec = (83333 / ADC.Read(0)) - 9  'compute delay based on center frequency  


End Function


'blinks LEDs to show current software version
Sub ShowVers()
'--------------
Dim
   j As Integer
   
   DelayMS(500)
   For j = 1 To Vers1
       TurnOn(-3)
       DelayMS(500)
       LEDoff
       DelayMS(500)
   Next
   
   DelayMS(500)
   For j = 1 To Vers2
       TurnOn(3)
       DelayMS(500)
       LEDoff
       DelayMS(500)
   Next
End Sub    
     


'=================
'Main code here
'=================

Initialize
InitializeADC
TestLEDs
ShowVers

Repeat
   #IfDef SerialDebug
       USART.Write(BinToStr(Fixed),CR,LF) 
   #EndIf
   
   'If DIP switch at $0, then execute Goertzel tone decoder algorithm
   While (ReadDip = Gmode) Or (ReadDip = GplusMode)      'switch is to ground so when switch = 0, PIC reads %1111 = $F
      Goertzel
   Wend
   
   'option here is to read the pot 
   If Fixed = PotFreq Then 
      CenterFreq = 3* ADC.Read(0) 'read the pot to get the low frequency edge
                          'range is thus approx 200 - 3200 Hz   
   End If
   
   
   'recall stored frequency from memory
   If Fixed = StoredFreq Then
          CenterFreq = EE.ReadWord(2*ReadDip)
   EndIf
                          
   Freq = GetFreq
                                
   'Delayms(10)
                                
   'selection is 25 or 50 Hz steps
   If SpanWidth = Step25Hz Then 
        LEDSel = (ComputeAvg(Freq) - CenterFreq) / LowStep
   Else
        LEDSel = (ComputeAvg(Freq) - CenterFreq) / HighStep
   End If     
   TurnOn(LEDSel)   'light the appropriate LED

   If SaveToMem = WriteToMem Then     'in mode to read ADC and write to memory
      EE.WriteWord(2*ReadDip,3* ADC.Read(0)) 'read the POT and save the value to EEPROM
   EndIf 
Until False


      
End 
