;-------------------------------------------------------------------------------
;16F684
;
;24 uurs timer
;instelbaar van (hh:mm:ss) 00:00:01 tot 23:59:59
;wanneer de teller gestart wordt op '00:00:00'
;telt hij af vanaf '00:59:59'
;-------------------------------------------------------------------------------
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   relais   is pin_A2
pin_A2_direction =  output
relais = off
alias   switch   is pin_A3
pin_A3_direction =  input

var volatile byte ProgFlags
var volatile bit sw_vorige   at ProgFlags : 0
var volatile bit sw_temp     at ProgFlags : 1
var volatile bit adc_window  at ProgFlags : 7
;schakelaar is laag-waar daarom uitgangspositie is high
sw_vorige = high
sw_temp   = high
;trigger om nieuwe waarde in te stellen
adc_window = high

;-------------------------------------------------------------------------------
;instellingen voor de timer
;-------------------------------------------------------------------------------
include pic_data_eeprom
var volatile byte Hours
var volatile byte Minutes
var volatile byte Seconds
var volatile byte Tmp
const byte BuzzerTime = 3
var byte BuzzerTimer = BuzzerTime

procedure LoadTime is
  ;gegevens opgeslagen in de EEPROM om tijd te herzetten?
  data_eeprom_read(0, Hours)
  if(Hours > 23)then
    Hours = 0
  end if
  data_eeprom_read(1, Minutes)
  if(Minutes > 59)then
    Minutes = 0
  end if
  data_eeprom_read(2, Seconds)
  if(Seconds > 59)then
    Seconds = 0
  end if
end procedure

;voer procedure uit
LoadTime

;-------------------------------------------------------------------------------
;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

const byte        TimerCounterReload = 50
var byte          TimerCounter = TimerCounterReload
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  FlagClear      at TimeFlags : 7
const byte        BlinkTimerReload = 20
var byte          BlinkTimer = BlinkTimerReload

;-------------------------------------------------------------------------------
;LCD declaratie en initialisatie
;-------------------------------------------------------------------------------
;Volgende constanten moeten gedeclareerd worden:
const byte LCD_ROWS     = 1               -- 1, 2 or 4 lines
const byte LCD_CHARS    = 8              -- 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

const byte Blank[] = "  "

;laad de eigenlijke bibliotheek
include lcd_hd44780_4
;en ionitialiseer 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)

;-------------------------------------------------------------------------------
;INTERRUPT PROCEDURE
;-------------------------------------------------------------------------------
procedure TimerInterrupt is
  pragma interrupt
  if(PIR1_TMR2IF)then
    PIR1_TMR2IF = low
    ;20ms
    Flag20msPassed = true
    ;teller resetten?
    if(FlagClear)then
      FlagClear = false
      TimerCounter = TimerCounterReload       ;herlaad de teller
    end if
    ;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
    ;laat dit x keer gebeuren om te knipperen
    BlinkTimer = BlinkTimer - 1
    if(BlinkTimer == 0)then
      BlinkTimer = BlinkTimerReload
      FlagBlink = true
      FlagBlinkState = !FlagBlinkState
    end if
  end if
end procedure

;-------------------------------------------------------------------------------
;status van de schakelaar
;-------------------------------------------------------------------------------
const byte STATE_NONE        = 0x00
const byte STATE_HOURS       = 0x01
const byte STATE_MINUTES     = 0x02
const byte STATE_SECONDS     = 0x03
const byte STATE_TIMER_READY = 0x10
const byte STATE_TIMER       = 0x20
const byte STATE_TIMER_END   = 0x40
const byte STATE_ABORT       = 0x80

;initiele toestand
var volatile byte SwitchState = STATE_NONE
var byte SwitchCounter = 0

const byte MAX_TIME = 25

;-------------------------------------------------------------------------------
;procedure om te tijd weer te geven
;-------------------------------------------------------------------------------
procedure ShowTime is
  ;zet de tijd op het display en knipper de eventuele eenheden of de volledige
  ;tijd
  lcd_cursor_position(0,0)
  if((SwitchState == STATE_TIMER_READY) & !FlagBlinkState)then
    ;knipper volledig display
    print_string(lcd, Blank)
    lcd_write_char(":")
    print_string(lcd, Blank)
    lcd_write_char(":")
    print_string(lcd, Blank)
  else
    ;edit individueel
    ;uren / knipperen?
    if(SwitchState == STATE_HOURS)then
      ;edit uren -> knipperen
      if(FlagBlinkState)then
        ;toon de uren
        if(Hours < 10)then
          lcd_write_char("0")
        end if
        print_byte_dec(lcd, Hours)
      else
        ;toon spaties
        print_string(lcd, Blank)
      end if
    else
      ;uren 'gewoon' weergeven
      if(Hours < 10)then
        lcd_write_char("0")
      end if
      print_byte_dec(lcd, Hours)
    end if
    lcd_write_char(":")
    ;minuten / knipperen?
    if(SwitchState == STATE_MINUTES)then
      ;edit minuten -> knipperen
      if(FlagBlinkState)then
        ;toon de minuten
        if(Minutes < 10)then
          lcd_write_char("0")
        end if
        print_byte_dec(lcd, Minutes)
      else
        ;toon spaties
        print_string(lcd, Blank)
      end if
    else
      ;minuten 'gewoon' weergeven
      if(Minutes < 10)then
        lcd_write_char("0")
      end if
      print_byte_dec(lcd, Minutes)
    end if
    lcd_write_char(":")
    ;seconden / knipperen?
    if(SwitchState == STATE_SECONDS)then
      ;edit seconden -> knipperen
      if(FlagBlinkState)then
        ;toon de seconden
        if(Seconds < 10)then
          lcd_write_char("0")
        end if
        print_byte_dec(lcd, Seconds)
      else
        ;toon spaties
        print_string(lcd, Blank)
      end if
    else
      ;seconden 'gewoon' weergeven
      if(Seconds < 10)then
        lcd_write_char("0")
      end if
      print_byte_dec(lcd, Seconds)
    end if
  end if
end procedure

;tijd weergeven
ShowTime

var volatile word wOldValue = 0
function CalcADCValue (byte in Limit, byte in CurrentValue) return byte is
  var volatile word wValue = 0
  var volatile word wTemp = 0
  var volatile byte Temp[2] at wTemp
  wValue = adc_read(1)
  ;bereken de nieuwe waarde
  wTemp = (wValue * Limit) / 1024
  if(adc_window)then
    ;er moet een ruim verschil zijn tussen huidige en vorige meting om een
    ;nieuw resultaat te geven
    if((wValue & 0xFF80) != (wOldValue & 0xFF80))then
      adc_window = low
    else
      ;geen ruim verschil, de vorige waarde wodt teruggekeerd
      Temp[0] = CurrentValue
    end if
  end if
  wOldValue = wValue
  return Temp[0]
end function

;-------------------------------------------------------------------------------
T2CON_TMR2ON = high    ;turn timer2 on;
INTCON_GIE = high      ;enable interrupts
;-------------------------------------------------------------------------------
forever loop
  ;Lees de schakelaar en verwerk
  sw_temp = switch
  ;debounce
  _usec_delay(10)
  if(sw_temp == switch)then
    ;stabiele toestand - ingedrukt?
    if(!sw_temp)then
      ;toets ingedrukt - pas ingedrukt?
      if(sw_temp != sw_vorige)then
        ;ja - start de teller
        SwitchCounter = 0
        sw_vorige = sw_temp
      end if
    else
      ;toets losgelaten - pas losgelaten?
      if(sw_temp != sw_vorige)then
        ;ja - verwerk gegevens
        if(SwitchCounter >= MAX_TIME)then
          ;GO! - ready of afbreken
          if(SwitchState == STATE_TIMER_READY)then
            SwitchState = STATE_NONE
            adc_window = high
          else
            SwitchState = STATE_TIMER_READY
            ;sla de gegevens op
            data_eeprom_write(0, Hours)
            data_eeprom_write(1, Minutes)
            data_eeprom_write(2, Seconds)
          end if
        else
          if(SwitchState <= STATE_SECONDS)then
            ;verhoog teller - none - hours - minutes - seconds - none - ...
            SwitchState = (SwitchState + 1) & STATE_SECONDS
            ;set de adc_window vlag - zo moet even aan de potmeter gedraaid
            ;worden om de huidige waarde te wijzigen
            adc_window = high
            ;wOldValue = wValue
          end if
          if(SwitchState == STATE_TIMER)then
            ;Afbreken
            SwitchState = STATE_ABORT
          end if
          if(SwitchState == STATE_TIMER_READY)then
            ;teller starten maar eerst zorgen dat de interruproutine de eerste
            ;seconde ook volledig telt
            FlagClear = high
            SwitchState = STATE_TIMER
          end if
          if(SwitchState == STATE_TIMER_END)then
            ;knipper resetten?
            SwitchState = STATE_NONE
            adc_window = high
          end if
        end if
        sw_vorige = sw_temp
      end if
    end if
  end if
  
  ;controlleer de status
  if(SwitchState == STATE_NONE)then
    Led = off
    Relais = off
  end if

  ;tijdseenheden alleen bewerken wanneer er een sereuze draai
  ;aan de potmeter werd gegeven en pas alle 20ms
  if(Flag20msPassed)then
    Flag20msPassed = low
    ;Geluid bij klikken langer aanhouden?
    BuzzerTimer = BuzzerTimer - 1
    if(BuzzerTimer == 0)then
      BuzzerTimer = BuzzerTime
      Led = off
    end if
    SwitchCounter = SwitchCounter + 1
    if(SwitchState == STATE_HOURS)then
      ;0-23
      Tmp = Hours
      Hours = CalcADCValue(24, Tmp)
      if(Tmp != Hours)then
        ;geef een signaal dat de instellingen veranderd zijn
        BuzzerTimer = BuzzerTime
        Led = on
      end if
    end if
    if(SwitchState == STATE_MINUTES)then
      ;0-59
      Tmp = Minutes
      Minutes = CalcADCValue(24, Tmp)
      if(Tmp != Minutes)then
        ;geef een signaal dat de instellingen veranderd zijn
        BuzzerTimer = BuzzerTime
        Led = on
      end if
    end if
    if(SwitchState == STATE_SECONDS)then
      ;0-59
      Tmp = Seconds
      Seconds = CalcADCValue(24, Tmp)
      if(Tmp != Seconds)then
        ;geef een signaal dat de instellingen veranderd zijn
        BuzzerTimer = BuzzerTime
        Led = on
      end if
    end if
  end if

  if(SwitchState == STATE_TIMER_READY)then
  end if

  if(SwitchState == STATE_TIMER)then
    ;start de timer
    Relais = on
    
    if(FlagTimePassed)then
      ;1 seconde is verstreken
      FlagTimePassed = low          ;wis de vlag
      ;Tijd aanpassen en weergeven
      Seconds = Seconds - 1
      if(Seconds > 59)then
        Seconds = 59
        Minutes = Minutes - 1
        if(Minutes > 59)then
          Minutes = 59
          if(Hours > 0)then
            Hours = Hours - 1
          end if
        end if
      end if
    end if
    ;volledige tijd afgelopen?
    Tmp = Hours + Minutes + Seconds
    if(Tmp == 0)then
      SwitchState = STATE_TIMER_END
      ;relais af!
      Relais = off
      ;gegevens opgeslagen in de EEPROM om tijd te herzetten?
      LoadTime
    end if
  end if

  if(SwitchState == STATE_TIMER_END)then
    ;geef een signaal dat de tijd verstreken is
    Led = FlagBlinkState
  end if

  if(SwitchState == STATE_ABORT)then
    ;relais af!
    Relais = off
    ;herbegin
    SwitchState = STATE_NONE
  end if
  ;display data
  ShowTime
end loop
;-------------------------------------------------------------------------------