;* --------------------------------------------------------------------------------------
;*  Copyright (C) 2004 Dallas Semiconductor Corporation, All Rights Reserved.
;* 
;*  Permission is hereby granted, free of charge, to any person obtaining a
;*  copy of this software and associated documentation files (the "Software"),
;*  to deal in the Software without restriction, including without limitation
;*  the rights to use, copy, modify, merge, publish, distribute, sublicense,
;*  and/or sell copies of the Software, and to permit persons to whom the
;*  Software is furnished to do so, subject to the following conditions:
;* 
;*  The above copyright notice and this permission notice shall be included
;*  in all copies or substantial portions of the Software.
;* 
;*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
;*  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;*  MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
;*  IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
;*  OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
;*  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
;*  OTHER DEALINGS IN THE SOFTWARE.
;* 
;*  Except as contained in this notice, the name of Dallas Semiconductor
;*  shall not be used except as stated in the Dallas Semiconductor
;*  Branding Policy.
;*
;* --------------------------------------------------------------------------------------
;* 
;*  This demonstration is meant to show several of the features of the MAXQ2000 and the 
;*  MAXQ2000 Evaluation Kit.  It shows how to configure and handle external interrupts, 
;*  how to read input ports, how to read and set the real-time clock, how to use 
;*  real-time clock alarms, and how to use the LCD controller to display meaningful
;*  data on the MAXQ2000 Evaluation Kit LCD Screen.
;*
;*  Consists of 2 portions.  The first is a simple counter application.  When the 
;*  application starts, you will see an incrementing value appear on the LCD screen.
;* 
;*  If you press the SW4 button, you will see the other portion of this application--
;*  the real-time clock display, with data formatted into hours and minutes.  It is 
;*  unlikely that the clock value is corret: use the SW5 button to increment the minutes, 
;*  and hold down the SW5 button and press the SW4 button to increment the hours.  Use 
;*  the SW4 button on its own to return to the counter application. (Note: the SW5 button 
;*  will also increment the minutes even if you are in counter mode)
;*
;*  Turn ON the following switches for this demonstration:
;*
;*       SW6.2, SW6.5: Enable the push buttons
;*       SW6.8 : Enable the LED panel
;*
;* --------------------------------------------------------------------------------------
#include "..\api\maxQ2000.inc"

;
; Define the soft stack for interrupt storage at 200h
;
SOFTSTACK_W EQU 200h

DISPLAYMODE_CLOCK   equ 1
DISPLAYMODE_COUNTER equ 2

 .code
 .keep
    ;
    ; Some basic initialization.  Set Accumulator Controls to normal.
    ; Set interrupt vector.  Set data pointer controls.  
    ;
    move  APC, #0                      ; no accumulator-modulo behavior
    move  AP, #0                       ; select accumulator 0
    move  DPC, #0                      ; select DP[0], byte mode
    move  IV, #w:interrupt_handler       ; set interrupt vector

    move  A[15], #DISPLAYMODE_COUNTER  ; A[15] is our flag for our display mode
    move  A[14], #0                    ; A[14] will contain our counter
    call  init_push_buttons            ; setup push button configuration
    call  init_lcd                     ; initialize the EV Kit LCD screen
    call  init_rtc                     ; start the real-time-clock running
    move  ACC, IMR                     ; need to mask in module 0 interrupts
    or    #1                           ; low bit --> module 0
    move  IMR, ACC                     ; store new interrupt mask
    move  IC, #1                       ; initialize global interrupts

;*****************************************************************************
;
; Main application loop.  Toggle the top two bits twice per loop.  Toggle the
; bottom bit once per loop.  Interrupt handler will take care of everything 
; else.
;
;*****************************************************************************
mainloop_top:
    ;
    ; Insert a simple delay loop here.
    ;
    move  LC[0], #8D80h                ; This gives us a good delay so we don't
    move  LC[1], #20h                  ;    see any skew on the seconds count
delay_loop:
    djnz  LC[0], delay_loop
    djnz  LC[1], delay_loop

    move  ACC, A[15]                   ; let's check our display mode
    cmp   #DISPLAYMODE_COUNTER         ; are we in counter display mode?
    jump  ne, main_dont_display_counter
    move  ACC, A[14]                   ; get our current counter
    call  lcd_shownumber

main_dont_display_counter:
    move  ACC, A[14]                   ; get our current counter
    sub   #19999                       ; if C clear, then we need to reset counter
    jump  c, main_counter_ok         
    move  A[14], #0                    ; reset our counter
    sjump main_end_loop                ; should be the end of the loop for us

main_counter_ok:
    move  ACC, A[14]                   ; get the current counter
    add   #1                           ; increment it
    move  A[14], ACC                   ; save the current counter
    
main_end_loop:
    jump  mainloop_top

;*****************************************************************************
;
; Initialize the push buttons.  We will be using push buttons 10 and 15 in 
; this application.  Set for falling edge triggered interrupts.  Enables 
; interrupts from module 1 in the system interrupt mask.
;
; Input:     None
; Output:    None
; Destroyed: ACC, PSW
;
;*****************************************************************************
init_push_buttons:
    move  EIE1, #84h                   ; enable interrupts for EI 10 and 15
    move  EIES1, #84h                  ; select falling edge for EI 10 and 15
    move  ACC, IMR                     ; get the interrupt mask enable
    or    #2                           ; include interrupts for module 1
    move  IMR, ACC                     ; store new interrupt mask enable value
    ret

;*****************************************************************************
;
; Displays the time to the LCD and updates the RTC alarm to be 1 second in 
; the future.
;
; Input:     None
; Output:    None
; Destroyed: Lots that are all restored by our interrupt handler
;
;*****************************************************************************
showTimeAndUpdateAlarm:
    call  rtc_getseconds               ; time is in A[2]:A[1]
    move  A[0], A[1]                   ; get set up for divide
    move  A[1], A[2]                   ;    divide total seconds by 86400
    move  A[2], #(86400 & 0ffffh)      ;    to get the number of seconds into
    move  A[3], #(86400 >> 16)         ;    today
    call  Div32                        ; seconds today is in A[3]:A[2]
    move  A[0], A[2]                   ; get set up for divide
    move  A[1], A[3]                   ;    divide number of seconds today by 3600
    move  A[2], #3600                  ;    to get us the number of hours today
    move  A[3], #0                     ;    and seconds left this hour
    call  Div32                        ; A[0] is our number of hours
    move  A[13], A[0]                  ; store our number of hours somewhere safe
    move  A[0], A[2]                   ; get set up for divide
    move  A[1], A[3]                   ;    divide by 60 to get number of minutes
    move  A[2], #60                    ;    into the hour
    move  A[3], #0
    call  Div32                        ; minutes is in A[0]
    move  A[1], A[13]                  ; restore number of hours
    move  A[13], A[2]                  ; store the number of seconds
    call  lcd_showtime                 ; show the time 
    move  ACC, A[13]                   ; restore the number of seconds
    and   #1                           ; get the bottom bit so we can toggle colon
    jump  z, showtime_updatealarm
    move  ACC, LCD4                    ; get the lcd control for the center colon
    xor   #10h                         ; toggle the center colon once per second
    move  LCD4, ACC                    ; write the new LCD control value

showtime_updatealarm:
    call  rtc_getseconds               ; get the current RTC values again
    move  AP, #1                       ; point to low byte of RTC values
    add   #1                           ; increment number of seconds
    move  AP, #2                       ; point to high byte of rtc values
    addc  #0                           ; carry over
    move  AP, #0                       ; return to accumulator normalcy
    call  rtc_setsecondsalarm          ; set the clock alarm for next second
    ret



;*****************************************************************************
;
; Interrupt handler for the application.  We are only expecting interrupts
; on external interrupt 10 and 15.  
;
; 
;*****************************************************************************
interrupt_handler:
    push  DPC
    move  DPC, #10h                    ; BP to word mode
    move  BP, #SOFTSTACK_W             ; storage stack at 200h
    move  OFFS, #0                     ; 0-offset into frame
    ;
    ; Start storing stuff on our soft stack
    ;
    move  @BP[OFFS], APC
    move  @BP[++OFFS], AP
    move  AP, #0
    move  @BP[++OFFS], LC[0]
    move  @BP[++OFFS], PSF
    move  APC, #4                      ; auto-inc AP
    move  LC[0], #15

interrupt_store_acc:
    move  @BP[++OFFS], ACC             ; store next accumulator
    djnz  LC[0], interrupt_store_acc   ; loop through 16 times to get all ACC's
    move  @BP[++OFFS], GR

    
    move  APC, #0
    move  AP, #0                       ; select A[3] for our accumulator
    move  ACC, EIF1                    ; get the interrupt flag
    and   #4                           ; check for interrupt 10
    jump  z, interrupt_not_10
    ;
    ; External interrupt 10 fired.  
    ;
    move  ACC, A[15]                   ; get our display mode
    cmp   #DISPLAYMODE_CLOCK           ; are we in clock display mode?
    jump  ne, interrupt_10_notclock
    ;
    ; We are in clock display mode.  Check to see if the other button is down.
    ;
    move  ACC, PI7                     ; grab input pins for port 7
    and   #02h                         ; look at P7.1
    jump  nz, interrupt_10_only        ; jump if other button not down
    ;
    ; External interrupt 10 fired AND the other button is down.
    ; Add 1 hour to the clock.
    ;
    call  rtc_getseconds               ; seconds are in A[2]:A[1]
    move  AP, #1                       ; point to accumulator 1
    add   #3600                        ; add 1 hour's worth of seconds
    move  AP, #2                       ; point to accumulator 2
    addc  #0                           ; add in the carry
    move  AP, #0                       ; restore accumulator normalcy
    call  rtc_setseconds               ; set the new current time
    move  RCNT, #3                     ; reenable all our alarms 'n' such
    call  showTimeAndUpdateAlarm       ; show updated time
    
    sjump interrupt_not_10             ; check the next interrupt condition

interrupt_10_only:
    ;
    ; toggle from clock to counter mode.
    ;
    move  A[15], #DISPLAYMODE_COUNTER  ; update the mode variable
    move  RCNT, #1                     ; clear our alarm enable
    sjump interrupt_not_10             ; check the next interrupt condition

interrupt_10_notclock:
    ;
    ; toggle from counter to clock mode.
    ;
    move  A[15], #DISPLAYMODE_CLOCK    ; update the mode variable
    call  showTimeAndUpdateAlarm       ; put the time on the LCD now
    move  RCNT, #3                     ; set our alarm
    ;
    ; Fallthrough to check if other interrupt fired too
    ;
interrupt_not_10:
    move  ACC, EIF1                    ; get the interrupt flag
    and   #80h                         ; check for interrupt 15
    jump  z, interrupt_not_15
    ;
    ; External interrupt 15 fired.  Add a minute to the clock.
    ;
    call  rtc_getseconds               ; seconds are in A[2]:A[1]
    move  AP, #1                       ; point to accumulator 1
    add   #60                          ; add 60 seconds
    move  AP, #2                       ; point to accumulator 2
    addc  #0                           ; add in the carry
    move  AP, #0                       ; restore accumulator normalcy
    call  rtc_setseconds               ; set the new current time
    move  RCNT, #3                     ; reenable all our alarms 'n' such
    call  showTimeAndUpdateAlarm       ; show updated time
    ;
    ; Fallthrough to check if other interrupt fired too
    ;
interrupt_not_15:
    move  EIF1, #0                     ; clear the interrupt conditions

    ;
    ; See if we got an RCNT firing...
    ;
    move  ACC, RCNT                    ; get RTC control
    and   #40h                         ; isolate the time of day alarm
    jump  z, interrupt_notrtc
    ;
    ; RTC interrupt fired
    ;
    call  showTimeAndUpdateAlarm
    call  wait_for_rtc                 ; wait for RTC to not be busy
    move  RCNT.6, #0                   ; clear the RTC alarm bit

interrupt_notrtc:
    ;
    ; Let's restore things from our soft stack
    ;
    move  GR, @BP[OFFS--]
    move  LC[0], #15
    move  APC, #0

interrupt_restore_acc:
    move  ACC, @BP[OFFS--]             ; store next accumulator
    djnz  LC[0], interrupt_restore_acc ; loop through 16 times to get all ACC's
    move  PSF, @BP[OFFS--]
    move  LC[0], @BP[OFFS--]
    move  AP, @BP[OFFS--]
    move  APC, @BP[OFFS--]

    pop   DPC
    reti

end
