;-------------------------------------------------------------------------------
;16F684
;
;Servotester
;versie 20120806
;-------------------------------------------------------------------------------
include 16f684

pragma target clock    4_000_000       ;oscillator frequency
pragma target OSC      XT              ;4MHz xTal
pragma target WDT      disabled        ;no watchdog
pragma target MCLR     internal        ;reset internally

enable_digital_io()    ;alle pinnen digitale IO

;LED is via voorschakelweerstand aangesloten op pin A0
alias   led      is pin_A0
pin_A0_direction =  output
led = off
alias   servo    is pin_A2
pin_A2_direction =  output
servo = off
alias   switch   is pin_A3
pin_A3_direction =  input

;-------------------------------------------------------------------------------
;instellingen voor Timer2
;Prescaler= 16
;PostScaler = 5
;PR2 = 250 - 1 voor herlaadtijd
;Freq = 50 Hz
;Tijd = 0.02 seconden
;-------------------------------------------------------------------------------
T2CON_TOUTPS = 0b0100  ;postscaler
T2CON_T2CKPS = 0b11
PR2          = 249     ;herlaadwaarde
PIE1_TMR2IE  = high    ;Timer2 interrupt enable
INTCON_PEIE  = high    ;perifierie interrupt enable
PIR1_TMR2IF  = low     ;wis interrupt vlag

var volatile byte TimeFlags = 0
var volatile bit  FlagTimePassed at TimeFlags : 0
var volatile bit  Flag20msPassed at TimeFlags : 1
var volatile bit  FlagBlink      at TimeFlags : 2
var volatile bit  FlagBlinkState at TimeFlags : 3
var volatile bit  FlagServoStep  at TimeFlags : 4
var volatile bit  FlagClear      at TimeFlags : 7
;een seconde
const byte        TimerCounterReload = 50
var byte          TimerCounter = TimerCounterReload
;Led knipper frequentie
const byte        BlinkTimerReload = 20
var byte          BlinkTimer = BlinkTimerReload
;Servo update frequentie
const byte        ServoStepReload = 10
var byte          ServoStepTimer = ServoStepReload

;-------------------------------------------------------------------------------
;LCD declaratie en initialisatie
;-------------------------------------------------------------------------------
;Volgende constanten moeten gedeclareerd worden:
const byte LCD_ROWS     = 2               -- 1, 2 or 4 lines
const byte LCD_CHARS    = 16              -- 8, 16 or 20 chars per line
;Alisassen voor de handshake-lijnen:
alias   lcd_rs          is  pin_c5        -- cmd/data select
alias   lcd_en          is  pin_c4        -- trigger
pin_C5_direction       = output
pin_C4_direction       = output
;Aliassen voor de vier datalijnen:
alias lcd_dataport  is  portC_low         -- 4 databits
pin_C0_direction       = output
pin_C1_direction       = output
pin_C2_direction       = output
pin_C3_direction       = output

;voor de aanduiding van 'micro' gebruiken we een 'u'
;het karakter 'mu' bestaat niet standaard op het display
;en kan eventueel zelf aangemaakt worden
const byte sTime[] = " us"

;laad de eigenlijke bibliotheek
include lcd_hd44780_4
;wacht even om trage LCD's te laten resetten
include delay
delay_100ms(5)
;en initialiseer het display
lcd_init()                            -- init the lcd controller
include print

;scherm wissen
lcd_clear_screen()

;-------------------------------------------------------------------------------
;ADC instellingen
;-------------------------------------------------------------------------------
const bit  ADC_HIGH_RESOLUTION = true
const byte ADC_NVREF = 0
;de bibliotheek laden
include adc
;De ADC initialiseren
adc_init()
;en maak pen 12 (RA1/AN1) analoge ingang
set_analog_pin(1)
var word wADCValue

;-------------------------------------------------------------------------------
;Servo en timer1 instellingen
;-------------------------------------------------------------------------------
var volatile word  wServoReload = 65535 - 1480
var volatile  byte ServoReload[2] at wServoReload
var volatile word  wServoValue = 1500
var byte NewServoValue = 0
var byte ServoDir      = 0
var word wServoStep    = 10

T1CON = 0b00000100
TMR1H = ServoReload[1]
TMR1L = ServoReload[0]

T1CON_T1CKPS  = 0          ;geen prescaler
T1CON_T1OSCEN = high       ;oscillator enable
T1CON_TMR1CS  = low        ;internal clock
T1CON_TMR1ON  = low        ;Timer1 off
T1CON_NT1SYNC = high       ;do not synchronize clock
PIE1_TMR1IE   = high
INTCON_PEIE   = high

;-------------------------------------------------------------------------------
;INTERRUPT PROCEDURE
;-------------------------------------------------------------------------------
procedure TimerInterrupt is
  pragma interrupt
  
  ;Timer 1 interrupt
  if(PIR1_TMR1IF)then
    ;servo puls afgelopen
    Servo = low
    ;stop de teller
    T1CON_TMR1ON = low
    ;reset Timer1 interruptvlag
    PIR1_TMR1IF = low
    ;Nieuwe waarde?
    if(NewServoValue > 0)then
      ;De tijd dat de servopluls 'actief' moet zijn (tussen 1000 en 2000 µs)
      ;wordt berekend door de van de maximale tellerwaarde (0xFFFF = 65536)
      ;de werkelijke waarde af te trekken.
      ;De waarde 20 die extra bijgeteld wordt is om de 'verloren' microseconden
      ;te compenseren die nodig zijn om tot in de interruptprocedure te geraken
      ;(oa saven van registers).
      wServoReload = 65535 - wServoValue + 20
    end if
    TMR1H = ServoReload[1]
    TMR1L = ServoReload[0]
  end if

  ;Timer 2 interrupt
  if(PIR1_TMR2IF)then
    ;20ms zijn om: zet de servo en de teller aan
    Servo = high
    T1CON_TMR1ON = high
    ;reset Timer2 interrruptvlag
    PIR1_TMR2IF = low
    ;meldt 20 ms gepasseerd
    Flag20msPassed = true
    ;laat dit  50 keer gebeuren voor 1 seconde
    TimerCounter = TimerCounter - 1
    if(TimerCounter == 0)then
      TimerCounter = TimerCounterReload       ;herlaad de teller
      FlagTimePassed = true                   ;tijd is verstreken
    end if
    ;ServoStepTimer
    ServoStepTimer = ServoStepTimer - 1
    if(ServoStepTimer == 0)then
      ServoStepTimer = ServoStepReload
      FlagServoStep = true
    end if
    ;Knipperteller
    BlinkTimer = BlinkTimer - 1
    if(BlinkTimer == 0)then
      BlinkTimer = BlinkTimerReload
      FlagBlink = true
    end if
  end if
  ;Einde interrupt
end procedure


;-------------------------------------------------------------------------------
;Instellingen voor de schakelaar
;-------------------------------------------------------------------------------
const byte SW_MODE_NEUTRAL = 0
const byte SW_MODE_RUN     = 1
const byte SW_MODE_MANUAL  = 2
var byte SW_MODE           = SW_MODE_NEUTRAL
var byte SW_MODE_CHANGE    = 1       ;voor initialisatie!

const byte SW_ON           = 1
const byte SW_OFF          = 0
var byte SW_STATE          = SW_OFF
var byte SW_PREVIOUS_STATE = SW_OFF
;-------------------------------------------------------------------------------
T2CON_TMR2ON = high    ;turn timer2 on;
INTCON_GIE   = high    ;enable interrupts
;-------------------------------------------------------------------------------
;Main loop
;-------------------------------------------------------------------------------
forever loop
  if(Flag20msPassed)then
    ;20ms zijn gepasseerd, reset de vlag
    Flag20msPassed = low
    if(SW_MODE == SW_MODE_MANUAL)then
      ;De stand van de potmeter wordt gebruikt voor de servowaard.
      ;Lees de ADC (10 bits -> waarde van 0 tot 1023)
      wADCValue = adc_read_high_res(1)
      ;de lsb hebben we niet nodig, zorgt voor jitter
      wADCValue = wADCValue >> 1
      ;We hebben nu een waarde van 0 tot 511.  Maal 2 geeft dit teur een
      ;waarde van 0 tot 1022 (die 1000 hebben we nodig voor 1ms)
      wADCValue = wADCValue + wADCValue
      ;Tel de offset bij voor een waarde van 1ms tot 2ms
      wServoValue = 1000 + wADCValue
      ;En zet die waarde op het display
      lcd_cursor_position(1,0)
      print_word_dec(lcd, wServoValue)
      print_string(lcd, sTime)
      ;geef de interruptroutine te kennen dat er een nieuwe waarde is
      NewServoValue = 1
    end if
    ;Schakelaar gewijzigd? (laag waar)
    if(Switch == high)then
      ;Als de vlaggen nog aan stonden dan afzetten
      if(SW_STATE == SW_ON)then
        SW_STATE = SW_OFF
        SW_PREVIOUS_STATE = SW_OFF
      end if
    end if
    if(Switch == low)then
      ;Als het indrukken van de schakelaar nog niet gemeld is, melden
      if(SW_STATE == SW_OFF)then
        ;dender onderdrukken
        if(SW_PREVIOUS_STATE == SW_OFF)then
          ;zet die vlag al en na 20 ms de eigenlijke vlag
          SW_PREVIOUS_STATE = SW_ON
        else
          ;de vorige keer was de schakelaar al ingedrukt, geen dender
          SW_STATE = SW_ON
          ;verhoog de MODE
          SW_MODE = SW_MODE + 1
          if(SW_MODE > SW_MODE_MANUAL)then
            SW_MODE = SW_MODE_NEUTRAL
          end if
          ;en meldt dit
          SW_MODE_CHANGE = 1
        end if
      end if
    end if
  end if

  if(SW_MODE_CHANGE != 0)then
    ;Mode is gewijzigd!
    ;initialiseer
    SW_MODE_CHANGE = 0
    if(SW_MODE == SW_MODE_NEUTRAL)then
      ;1500 us
      ;zet 1500 op de bovenste regel
      lcd_clear_screen()
      wServoValue = 1500
      lcd_cursor_position(0,0)
      print_word_dec(lcd, wServoValue)
      print_string(lcd, sTime)
      NewServoValue = 1
    end if
    if(SW_MODE == SW_MODE_RUN)then
      ;begin neutraal en verhoog; de juiste stand staat al op de LCD
      wServoValue = 1500
      ServoDir = 1
    end if
    if(SW_MODE == SW_MODE_MANUAL)then
      ;Wis het display en vanaf nu is er elke 20 ms een update
      lcd_clear_screen()
    end if
  end if

  if(FlagServoStep)then
    if(SW_MODE == SW_MODE_RUN)then
      ;pas de servo aan (enkel in RUN modus)
      if(ServoDir == 0)then
        ;aflopend
        if((wServoValue  - wServoStep) >= 1000)then
          wServoValue = wServoValue - wServoStep
          NewServoValue = 1
        else
          ServoDir = 1
        end if
      else
        ;oplopend
        if((wServoValue + wServoStep) <= 2000)then
          wServoValue = wServoValue + wServoStep
          NewServoValue = 1
        else
          ServoDir = 0
        end if
      end if
      lcd_cursor_position(0,0)
      print_word_dec(lcd, wServoValue)
      print_string(lcd, sTime)
    end if
    FlagServoStep = low
  end if

  if(FlagBlink)then
    ;knipper de led
    Led = !Led
    FlagBlink = low
  end if

  if(FlagTimePassed)then
    ;1 seconde
    FlagTimePassed = low
  end if
  ;Einde mail loop
end loop
;-------------------------------------------------------------------------------