Kjetil's Information Center: A Blog About My Projects

AVR Alarm Clock

Here is an implementation of an alarm clock running on an Atmel AVR (ATMega8) microcontroller. It uses a 8-digit LCD display for output, a 3x4 button keypad for input and a buzzer to give an alarm.

Here is a circuit diagram of the whole setup:

AVR Alarm Clock Schematics

Get a higher resolution schematic here.

The software on the microcontroller is written entirely in AVR Assembly language. It features a menu to view/set the clock and view/set/enable the alarm and the buzzer. The clock itself is updated by a timer interrupt in the microcontroller, and is not very accurate when running on the internal clock unfortunately. Perhaps an external crystal would improve it, but I never got around to trying it due to lack of hardware.

I used the AVRA assembler and AVRDUDE utility against a Atmel STK500 board for development. Easy with this Makefile:

clock.hex clock.eep.hex: clock.asm
	avra clock.asm -o clock.hex

# Needs to be in the correct order, Flash first, then EEPROM second.
.PHONY: install
install: clock.hex clock.eep.hex
	avrdude -c stk500v2 -p m8 -P /dev/ttyS0 -U flash:w:clock.hex:i
	avrdude -c stk500v2 -p m8 -P /dev/ttyS0 -U eeprom:w:clock.eep.hex:i

.PHONY: clean
clean:
	rm clock.hex clock.eep.hex clock.obj clock.cof
          


Here is the code (clock.asm):

; *****************************************************************************
;  AVR ALARM CLOCK
; *****************************************************************************

.NOLIST
.INCLUDE "m8def.inc" ; ATMega8
.LIST

; =============================================================================
;  REGISTER DEFINITIONS
; =============================================================================

.DEF second = r0 ; Clock second.
.DEF minute = r1 ; Clock minute.
.DEF hour   = r2 ; Clock hour.

.DEF alarm_active = r3 ; Alarm active/enable flag.
.DEF alarm_minute = r4 ; Alarm minute.
.DEF alarm_hour   = r5 ; Alarm hour.

.DEF var1   = r16 ; Local use in routines.
.DEF var2   = r17 ; Local use in routines.
.DEF arg1   = r18 ; Argument (or return value) between routines.
.DEF arg2   = r19 ; Argument (or return value) between routines.
.DEF intvar = r20 ; Used only in interrupt routines.

; =============================================================================
;  VALUE LABELS
; =============================================================================

.EQU MENU_ENTRY_CLOCK_SHOW    = 0
.EQU MENU_ENTRY_CLOCK_SET     = 1
.EQU MENU_ENTRY_ALARM_SET     = 2
.EQU MENU_ENTRY_ALARM_ENABLE  = 3
.EQU MENU_ENTRY_BUZZER_STATUS = 4
.EQU MENU_ENTRY_END           = 5

.EQU CLOCK_SET_HOUR_MSD   = 0
.EQU CLOCK_SET_HOUR_LSD   = 1
.EQU CLOCK_SET_MINUTE_MSD = 2
.EQU CLOCK_SET_MINUTE_LSD = 3
.EQU CLOCK_SET_SECOND_MSD = 4
.EQU CLOCK_SET_SECOND_LSD = 5
.EQU CLOCK_SET_END        = 6

.EQU ALARM_SET_HOUR_MSD   = 0
.EQU ALARM_SET_HOUR_LSD   = 1
.EQU ALARM_SET_MINUTE_MSD = 2
.EQU ALARM_SET_MINUTE_LSD = 3
.EQU ALARM_SET_END        = 4

; =============================================================================
;  DATA DEFINITIONS
; =============================================================================

.DSEG
.ORG 0x0060
_keypad_key_previous: .BYTE 1

; =============================================================================
;  EEPROM DATA
; =============================================================================

.ESEG
.ORG 0x0000

clock_set_menu_text:
.db "ClockSet"
alarm_set_menu_text:
.db "AT:"
alarm_enable_menu_text:
.db "AE:"
buzzer_status_menu_text:
.db "Buzzer:"

; =============================================================================
;  RESET AND INTERRUPT VECTORS
; =============================================================================

.CSEG
.ORG 0x0000 ; Reset vector.
  rjmp main_init

.ORG 0x0006 ; Timer1 compare 'a' interrupt.
  rjmp timer1_interrupt

; =============================================================================
;  MAIN INITIALIZATION
; =============================================================================

.ORG 0x0010
main_init:
  ; Setup MCU control register.
  in var1, MCUCR
  ori var1, 0x80 ; Enable sleep in idle mode.
  out MCUCR, var1

  ; Define SRAM as stack. (In order to use subroutines.)
  ldi var1, HIGH(RAMEND)
  out SPH, var1
  ldi var1, LOW(RAMEND)
  out SPL, var1

  ; Clear registers.
  clr second
  clr minute
  clr hour
  clr alarm_active
  clr alarm_minute
  clr alarm_hour

  ; Clear data.
  ldi var1, 0
  sts _keypad_key_previous, var1

  ; Set parts of Port-B (Connected to LCD) as output.
  ldi var1, 0b00111111
  out DDRB, var1

  ; Set parts of Port D (Connected to Keypad and buzzer) as output.
  ldi var1, 0b10010101
  out DDRD, var1

  ; Enable compare match 'a' interrupt for Timer1.
  ldi var1, 0b00010000
  out TIMSK, var1

  ; Set compare value 'a' for Timer1.
  ; (Tuned to one second when using internal clock.)
  ldi var1, 0b00111100
  out OCR1AH, var1
  ldi var1, 0b10000000
  out OCR1AL, var1

  ; Clear Timer1 counter.
  clr var1
  out TCNT1H, var1
  out TCNT1L, var1

  ; Enable interrupts globally. 
  sei

  ; Start Timer1 in CTC mode and use internal clock with clk/64 prescaler.
  clr var1
  out TCCR1A, var1
  ldi var1, 0b00001011
  out TCCR1B, var1

  ; Initialize LCD.
  rcall lcd_init

  ; Start main program loop.
  rjmp menu_loop_init

; =============================================================================
;  MAIN MENU LOOP
; =============================================================================

menu_loop_init:
  clr var1 ; Holds "current entry". What to show and what action to take.
menu_loop:

  cpi var1, MENU_ENTRY_CLOCK_SHOW
  brne _menu_loop_display_clock_set

  ; Display clock.
  rcall lcd_first_position
  mov arg1, hour
  rcall split_msd_lsd
  rcall lcd_number
  ldi arg1, ':'
  rcall lcd_character
  mov arg1, minute
  rcall split_msd_lsd
  rcall lcd_number
  ldi arg1, ':'
  rcall lcd_character
  mov arg1, second
  rcall split_msd_lsd
  rcall lcd_number

; -----------------------------------------------------------------------------

_menu_loop_display_clock_set:
  cpi var1, MENU_ENTRY_CLOCK_SET
  brne _menu_loop_display_alarm_set

  ; Display "ClockSet".
  rcall lcd_first_position
  ldi arg1, LOW(clock_set_menu_text)
  ldi arg2, 8
  rcall lcd_eeprom_string
  rjmp _menu_loop_keypad_poll

; -----------------------------------------------------------------------------

_menu_loop_display_alarm_set:
  cpi var1, MENU_ENTRY_ALARM_SET
  brne _menu_loop_display_alarm_enable

  ; Display "AT:" and time of alarm.
  rcall lcd_first_position
  ldi arg1, LOW(alarm_set_menu_text)
  ldi arg2, 3
  rcall lcd_eeprom_string

  mov arg1, alarm_hour
  rcall split_msd_lsd
  rcall lcd_number
  ldi arg1, ':'
  rcall lcd_character
  mov arg1, alarm_minute
  rcall split_msd_lsd
  rcall lcd_number
  rjmp _menu_loop_keypad_poll

; -----------------------------------------------------------------------------

_menu_loop_display_alarm_enable:
  cpi var1, MENU_ENTRY_ALARM_ENABLE
  brne _menu_loop_display_buzzer_status

  ; Display "AE:" and 1 if active, or 0 if not active.
  rcall lcd_first_position
  ldi arg1, LOW(alarm_enable_menu_text)
  ldi arg2, 3
  rcall lcd_eeprom_string

  tst alarm_active
  breq _menu_loop_display_alarm_not_active

  ldi arg1, '1'
  rcall lcd_character
  ldi arg1, ' '
  rcall lcd_character
  rcall lcd_character
  rcall lcd_character
  rcall lcd_character
  rjmp _menu_loop_keypad_poll

_menu_loop_display_alarm_not_active:
  ldi arg1, '0'
  rcall lcd_character
  ldi arg1, ' '
  rcall lcd_character
  rcall lcd_character
  rcall lcd_character
  rcall lcd_character
  rjmp _menu_loop_keypad_poll

; -----------------------------------------------------------------------------

_menu_loop_display_buzzer_status:
  cpi var1, MENU_ENTRY_BUZZER_STATUS
  brne _menu_loop_keypad_poll

  ; Display "Buzzer:" and 1 if active, or 0 if not active.
  rcall lcd_first_position
  ldi arg1, LOW(buzzer_status_menu_text)
  ldi arg2, 7
  rcall lcd_eeprom_string

  rcall buzzer_is_active
  tst arg1
  breq _menu_loop_display_buzzer_status_not_active

  ldi arg1, '1'
  rcall lcd_character
  rjmp _menu_loop_keypad_poll

_menu_loop_display_buzzer_status_not_active:
  ldi arg1, '0'
  rcall lcd_character
  rjmp _menu_loop_keypad_poll

; -----------------------------------------------------------------------------

_menu_loop_keypad_poll:
  rcall keypad_poll
  cpi arg1, '*'
  brne _menu_loop_keypad_poll_action
  inc var1
  cpi var1, MENU_ENTRY_END
  brge _menu_loop_wrap
  rjmp menu_loop
_menu_loop_wrap:
  clr var1
  rjmp menu_loop

_menu_loop_keypad_poll_action:
  cpi arg1, '#'
  breq _menu_loop_action_clock_set
  rjmp menu_loop

_menu_loop_action_clock_set:
  cpi var1, MENU_ENTRY_CLOCK_SET
  brne _menu_loop_action_alarm_set
  rcall clock_set_action
  rjmp menu_loop

_menu_loop_action_alarm_set:
  cpi var1, MENU_ENTRY_ALARM_SET
  brne _menu_loop_action_alarm_enable
  rcall alarm_set_action
  rjmp menu_loop

_menu_loop_action_alarm_enable:
  cpi var1, MENU_ENTRY_ALARM_ENABLE
  brne _menu_loop_action_buzzer_status
  rcall alarm_enable_action
  rjmp menu_loop

_menu_loop_action_buzzer_status:
  cpi var1, MENU_ENTRY_BUZZER_STATUS
  brne _menu_loop_end
  rcall buzzer_status_action
  rjmp menu_loop

_menu_loop_end:
  rjmp menu_loop

; =============================================================================
;  CLOCK SET SUB-MENU
; =============================================================================

; clock_set_action -- Adjust time of the clock.
; IN: N/A
; OUT: N/A
clock_set_action:
  push var1
  cli ; Freeze time while adjusting clock.
  clr var1 ; Holds current digit to be adjusted (0 to 5).

_clock_set_loop:
  mov arg1, var1
  rcall clock_set_display

  rcall keypad_poll

  ; Check if action should be aborted.
  cpi arg1, '#'
  breq _clock_set_end
  cpi arg1, '*'
  breq _clock_set_end

  ; Check a key between '0' and '9' is pressed, only then perform adjust.
  cpi arg1, 0x30
  brlt _clock_set_loop
  cpi arg1, 0x40
  brge _clock_set_loop
  mov arg2, var1
  rcall clock_set_update_digit

  inc var1
  cpi var1, CLOCK_SET_END
  breq _clock_set_end
  rjmp _clock_set_loop

_clock_set_end:
  sei ; Un-freeze time.
  pop var1
  ret

; -----------------------------------------------------------------------------

; clock_set_update_digit -- Update individual digit on clock manually.
; IN: arg1 -> ASCII value of digit to update.
;     arg2 -> Digit location from 0 to 5.
; OUT: N/A
clock_set_update_digit:
  subi arg1, 0x30 ; Convert from ASCII to actual integer value.

  cpi arg2, CLOCK_SET_HOUR_MSD
  brne _clock_set_update_hour_lsd
  rcall convert_to_msd
  mov hour, arg1
  ret

_clock_set_update_hour_lsd:
  cpi arg2, CLOCK_SET_HOUR_LSD
  brne _clock_set_update_minute_msd
  add hour, arg1
  ret

_clock_set_update_minute_msd:
  cpi arg2, CLOCK_SET_MINUTE_MSD
  brne _clock_set_update_minute_lsd
  rcall convert_to_msd
  mov minute, arg1
  ret

_clock_set_update_minute_lsd:
  cpi arg2, CLOCK_SET_MINUTE_LSD
  brne _clock_set_update_second_msd
  add minute, arg1
  ret

_clock_set_update_second_msd:
  cpi arg2, CLOCK_SET_SECOND_MSD
  brne _clock_set_update_second_lsd
  rcall convert_to_msd
  mov second, arg1
  ret

_clock_set_update_second_lsd:
  cpi arg2, CLOCK_SET_SECOND_LSD
  brne _clock_set_update_digit_end
  add second, arg1
  ret

_clock_set_update_digit_end:
  ret

; -----------------------------------------------------------------------------

; clock_set_display -- Display "update in progress" clock.
; IN: arg1 -> Digit location from 0 to 5. (What will be updated next.)
; OUT: N/A
clock_set_display:
  push var1
  mov var1, arg1

  rcall lcd_first_position

  mov arg1, hour
  rcall split_msd_lsd
  subi arg1, -0x30
  subi arg2, -0x30

  cpi var1, CLOCK_SET_HOUR_MSD
  brne _clock_set_display_hour_msd
  ldi arg1, '_' ; Use underscore to display "current" number to update.
_clock_set_display_hour_msd:
  rcall lcd_character

  mov arg1, arg2
  cpi var1, CLOCK_SET_HOUR_LSD
  brne _clock_set_display_hour_lsd
  ldi arg1, '_'

_clock_set_display_hour_lsd:
  rcall lcd_character

  ldi arg1, ':'
  rcall lcd_character

  mov arg1, minute
  rcall split_msd_lsd
  subi arg1, -0x30
  subi arg2, -0x30

  cpi var1, CLOCK_SET_MINUTE_MSD
  brne _clock_set_display_minute_msd
  ldi arg1, '_'
_clock_set_display_minute_msd:
  rcall lcd_character

  mov arg1, arg2
  cpi var1, CLOCK_SET_MINUTE_LSD
  brne _clock_set_display_minute_lsd
  ldi arg1, '_'

_clock_set_display_minute_lsd:
  rcall lcd_character

  ldi arg1, ':'
  rcall lcd_character

  mov arg1, second
  rcall split_msd_lsd
  subi arg1, -0x30
  subi arg2, -0x30

  cpi var1, CLOCK_SET_SECOND_MSD
  brne _clock_set_display_second_msd
  ldi arg1, '_'
_clock_set_display_second_msd:
  rcall lcd_character

  mov arg1, arg2
  cpi var1, CLOCK_SET_SECOND_LSD
  brne _clock_set_display_second_lsd
  ldi arg1, '_'

_clock_set_display_second_lsd:
  rcall lcd_character

  pop var1
  ret

; =============================================================================
;  ALARM SET SUB-MENU
; =============================================================================

; alarm_set_action -- Adjust the time of the alarm.
; IN: N/A
; OUT: N/A
alarm_set_action:
  push var1
  clr var1 ; Holds current digit to be adjusted (0 to 5).

_alarm_set_loop:
  mov arg1, var1
  rcall alarm_set_display

  rcall keypad_poll

  ; Check if action should be aborted.
  cpi arg1, '#'
  breq _alarm_set_end
  cpi arg1, '*'
  breq _alarm_set_end

  ; Check a key between '0' and '9' is pressed, only then perform adjust.
  cpi arg1, 0x30
  brlt _alarm_set_loop
  cpi arg1, 0x40
  brge _alarm_set_loop
  mov arg2, var1
  rcall alarm_set_update_digit

  inc var1
  cpi var1, ALARM_SET_END
  breq _alarm_set_end
  rjmp _alarm_set_loop

_alarm_set_end:
  pop var1
  ret

; -----------------------------------------------------------------------------

; alarm_set_update_digit -- Update individual digit on alarm.
; IN: arg1 -> ASCII value of digit to update.
;     arg2 -> Digit location from 0 to 5.
; OUT: N/A
alarm_set_update_digit:
  subi arg1, 0x30 ; Convert from ASCII to actual integer value.

  cpi arg2, ALARM_SET_HOUR_MSD
  brne _alarm_set_update_hour_lsd
  rcall convert_to_msd
  mov alarm_hour, arg1
  ret

_alarm_set_update_hour_lsd:
  cpi arg2, ALARM_SET_HOUR_LSD
  brne _alarm_set_update_minute_msd
  add alarm_hour, arg1
  ret

_alarm_set_update_minute_msd:
  cpi arg2, ALARM_SET_MINUTE_MSD
  brne _alarm_set_update_minute_lsd
  rcall convert_to_msd
  mov alarm_minute, arg1
  ret

_alarm_set_update_minute_lsd:
  cpi arg2, ALARM_SET_MINUTE_LSD
  brne _alarm_set_update_digit_end
  add alarm_minute, arg1
  ret

_alarm_set_update_digit_end:
  ret

; -----------------------------------------------------------------------------

; alarm_set_display -- Display "update in progress" for alarm.
; IN: arg1 -> Digit location from 0 to 5. (What will be updated next.)
; OUT: N/A
alarm_set_display:
  push var1
  mov var1, arg1

  rcall lcd_first_position

  mov arg1, alarm_hour
  rcall split_msd_lsd
  subi arg1, -0x30
  subi arg2, -0x30

  cpi var1, ALARM_SET_HOUR_MSD
  brne _alarm_set_display_hour_msd
  ldi arg1, '_' ; Use underscore to display "current" number to update.
_alarm_set_display_hour_msd:
  rcall lcd_character

  mov arg1, arg2
  cpi var1, ALARM_SET_HOUR_LSD
  brne _alarm_set_display_hour_lsd
  ldi arg1, '_'

_alarm_set_display_hour_lsd:
  rcall lcd_character

  ldi arg1, ':'
  rcall lcd_character

  mov arg1, alarm_minute
  rcall split_msd_lsd
  subi arg1, -0x30
  subi arg2, -0x30

  cpi var1, ALARM_SET_MINUTE_MSD
  brne _alarm_set_display_minute_msd
  ldi arg1, '_'
_alarm_set_display_minute_msd:
  rcall lcd_character

  mov arg1, arg2
  cpi var1, ALARM_SET_MINUTE_LSD
  brne _alarm_set_display_minute_lsd
  ldi arg1, '_'

_alarm_set_display_minute_lsd:
  rcall lcd_character

  ldi arg1, ' '
  rcall lcd_character
  rcall lcd_character
  rcall lcd_character

  pop var1
  ret

; =============================================================================
;  ALARM STATUS SUB-MENU
; =============================================================================

; alarm_enable_action -- Toggle "alarm_active" flag.
; IN: N/A
; OUT: N/A
alarm_enable_action:
  push var1

  tst alarm_active
  breq _alarm_enable_action_not_active
  clr var1
  mov alarm_active, var1

  pop var1
  ret

_alarm_enable_action_not_active:
  ser var1
  mov alarm_active, var1

  pop var1
  ret

; =============================================================================
;  BUZZER STATUS SUB-MENU
; =============================================================================

; buzzer_status_action -- Toggle buzzer.
; IN: N/A
; OUT: N/A
buzzer_status_action:
  rcall buzzer_is_active
  tst arg1
  breq _buzzer_status_action_not_active
  rcall buzzer_deactivate
  ret
_buzzer_status_action_not_active:
  rcall buzzer_activate
  ret

; =============================================================================
;  LCD DISPLAY INTERFACE
; =============================================================================

; lcd_init -- Initialize LCD display.
; IN: N/A
; OUT: N/A
lcd_init:
  push var1

  ; Wait a little after MC startup.
  rcall long_delay

  ; Set 4-bit data mode. (Directly, since interfaces does not work yet.)
  ldi var1, 0b00000010
  out PORTB, var1
  sbi PORTB, 4 ; Set enable.
  rcall short_delay
  cbi PORTB, 4 ; Clear enable.
  rcall long_delay

  ; Trick to keep to keep brightness correct.
  ldi arg1, 0b10001000
  rcall lcd_command
  ldi arg1, '!'
  rcall lcd_character

  ; Set 1-line mode.
  ldi arg1, 0b00100000
  rcall lcd_command

  ; Turn on LCD. (Without blinking cursor.)
  ldi arg1, 0b00001100
  rcall lcd_command

  ; Clear whole display.
  ldi arg1, 0b00000001 
  rcall lcd_command
  rcall long_delay

  ; Set cursor to move left after write operation.
  ldi arg1, 0b00000110
  rcall lcd_command

  pop var1
  ret

; -----------------------------------------------------------------------------

; lcd_character -- Print single character (extended ASCII) to LCD display.
; IN: arg1 -> character
; OUT: N/A
lcd_character:
  push var1

  ; Extract high nibble from byte.
  mov var1, arg1
  swap var1 ; Swap nibbles.
  andi var1, 0xf ; Clear high nibble.
  out PORTB, var1
  sbi PORTB, 5 ; Set RS to 1 to use character register.

  ; Toggle enable to send the first 4 bits of data.
  sbi PORTB, 4
  rcall short_delay
  cbi PORTB, 4
  rcall short_delay
  
  ; Extract lower nibble from byte.
  mov var1, arg1
  andi var1, 0xf
  out PORTB, var1
  sbi PORTB, 5

  ; Toggle enable to send the second 4 bits of data.
  sbi PORTB, 4
  rcall short_delay
  cbi PORTB, 4
  rcall short_delay

  pop var1
  ret

; -----------------------------------------------------------------------------

; lcd_command -- Send control command to LCD display.
; IN: arg1 -> command number
; OUT: N/A
lcd_command:
  push var1

  ; Extract high nibble from byte.
  mov var1, arg1
  swap var1 ; Swap nibbles.
  andi var1, 0xf ; Clear high nibble.
  out PORTB, var1
  ; RS remains 0 to use command register.

  ; Toggle enable to send the first 4 bits of data.
  sbi PORTB, 4
  rcall short_delay
  cbi PORTB, 4
  rcall short_delay
  
  ; Extract lower nibble from byte.
  mov var1, arg1
  andi var1, 0xf
  out PORTB, var1

  ; Toggle enable to send the second 4 bits of data.
  sbi PORTB, 4
  rcall short_delay
  cbi PORTB, 4
  rcall short_delay

  pop var1
  ret

; -----------------------------------------------------------------------------

; lcd_first_position -- Set LCD address to first position (0).
; IN: N/A
; OUT: N/A
lcd_first_position:
  ldi arg1, 0b10000000 ; Set DDRAM address to first display position.
  rcall lcd_command
  ret

; -----------------------------------------------------------------------------

; lcd_number -- Print two digit number to LCD display.
; IN: arg1 -> MSD of two digit number.
;     arg2 -> LSD of two digit number.
; OUT: N/A
lcd_number:
  ; Add ASCII compensator.
  subi arg1, -0x30
  subi arg2, -0x30

  rcall lcd_character
  mov arg1, arg2
  rcall lcd_character

  ret

; -----------------------------------------------------------------------------

; lcd_eeprom_string -- Print string from EEPROM to LCD display.
; IN: arg1 -> Start of EEPROM address.
;     arg2 -> Amount of characters in string.
; OUT: N/A
lcd_eeprom_string:
  push var1
  push var2
  mov var1, arg1
  mov var2, arg2

_lcd_eeprom_string_loop:
  mov arg1, var1
  rcall eeprom_read
  rcall lcd_character
  inc var1 ; Address incremented for every cycle.
  dec var2 ; Will be decremented until zero.
  tst var2
  brne _lcd_eeprom_string_loop

  pop var2
  pop var1
  ret

; =============================================================================
;  KEYPAD INTERFACE
; =============================================================================

; keypad_poll -- Retrive next keypress from keypad.
; IN: N/A
; OUT: arg1 -> Pressed key in ASCII, or 0 if no new key has been pressed.
keypad_poll:
  push var1
  clr arg1

  ; ldi var1, 0b00000100 ; K1
  ; out PORTD, var1
  sbi PORTD, 2 ; K1
  nop ; This NOP is required for correct timing.
  sbic PIND, 1 ; R1
  ldi arg1, '1'
  sbic PIND, 6 ; R2
  ldi arg1, '4'
  sbic PIND, 5 ; R3
  ldi arg1, '7'
  sbic PIND, 3 ; R4
  ldi arg1, '*'
  cbi PORTD, 2 ; K1

  ; ldi var1, 0b00000001 ; K2
  ; out PORTD, var1
  sbi PORTD, 0 ; K2
  nop
  sbic PIND, 1 ; R1
  ldi arg1, '2'
  sbic PIND, 6 ; R2
  ldi arg1, '5'
  sbic PIND, 5 ; R3
  ldi arg1, '8'
  sbic PIND, 3 ; R4
  ldi arg1, '0'
  cbi PORTD, 0 ; K2

  ; ldi var1, 0b00010000 ; K3
  ; out PORTD, var1
  sbi PORTD, 4 ; K3
  nop
  sbic PIND, 1 ; R1
  ldi arg1, '3'
  sbic PIND, 6 ; R2
  ldi arg1, '6'
  sbic PIND, 5 ; R3
  ldi arg1, '9'
  sbic PIND, 3 ; R4
  ldi arg1, '#'
  cbi PORTD, 4 ; K3

  lds var1, _keypad_key_previous
  cp arg1, var1
  brne _keypad_return_new_key
  clr arg1
  pop var1
  ret ; Return 0 to indicate NO new key.

_keypad_return_new_key:
  sts _keypad_key_previous, arg1
  pop var1
  ret

; =============================================================================
;  BUZZER INTERFACE
; =============================================================================

; buzzer_activate -- Start buzzer.
; IN: N/A
; OUT: N/A
buzzer_activate:
  sbi PORTD, 7
  ret

; -----------------------------------------------------------------------------

; buzzer_deactivate -- Stop buzzer.
; IN: N/A
; OUT: N/A
buzzer_deactivate:
  cbi PORTD, 7
  ret

; -----------------------------------------------------------------------------

; buzzer_is_active -- Fetch current status of buzzer (output).
; IN: N/A
; OUT: arg1 -> 1 if active, or 0 if deactivated.
buzzer_is_active:
  ldi arg1, 0
  sbic PORTD, 7
  ldi arg1, 1
  ret

; =============================================================================
;  COMMON ROUTINES
; =============================================================================

; eeprom_read -- Read from EEPROM.
; IN: arg1 -> EEPROM address
; OUT: arg1 -> value
eeprom_read:
  sbic EECR, 1 ; Check if EEPROM is busy.
  rjmp eeprom_read
  out EEARL, arg1 ; Select EEPROM address register.
  ldi arg1, 0
  out EEARH, arg1
  sbi EECR, EERE ; Activate "Read Enable".
  in arg1, EEDR ; Read from data register.
  ret

; -----------------------------------------------------------------------------

; long_delay -- Waste CPU cycles to cause a "long" delay.
; IN: N/A
; OUT: N/A
long_delay:
  push var1
  push var2
_long_delay_loop:
  dec var1
  brne _long_delay_loop
  dec var2
  brne _long_delay_loop
  pop var2
  pop var1
  ret

; -----------------------------------------------------------------------------

; short_delay -- Waste CPU cycles to cause a "short" delay.
; IN: N/A
; OUT: N/A
short_delay:
  push var1
_short_delay_loop:
  dec var1
  brne _short_delay_loop
  pop var1
  ret

; -----------------------------------------------------------------------------

; split_msd_lsd -- Split two digit number into MSD and LSD.
; IN: arg1 -> Two digit number.
; OUT: arg1 -> MSD
;      arg2 -> LSD
split_msd_lsd:
  mov arg2, arg1
  ldi arg1, -1
_split_msd_lsd_positive:
  inc arg1
  subi arg2, 10
  brpl _split_msd_lsd_positive
  subi arg2, -10 ; Actually same as "addi arg1, 10", which does not exist.
  ret

; -----------------------------------------------------------------------------

; convert_to_lsd -- Convert LSD to MSD.
; IN: arg1 -> LSD
; OUT: arg1 -> MSD
convert_to_msd:
  push var1
  mov var1, arg1
_convert_to_msd_loop:
  cpi var1, 0
  breq _convert_to_msd_done
  subi arg1, -9
  dec var1
  rjmp _convert_to_msd_loop
_convert_to_msd_done:
  pop var1
  ret

; =============================================================================
;  INTERRUPT ROUTINES
; =============================================================================

; timer1_interrupt -- Updates clock (second/minute/hour) counters.
; IN: N/A
; OUT: N/A
timer1_interrupt:
  inc second
  ldi intvar, 60
  cp second, intvar
  brlt _timer1_check_alarm
  clr second
  inc minute
  cp minute, intvar
  brlt _timer1_check_alarm
  clr minute
  inc hour
  ldi intvar, 24
  cp hour, intvar
  brlt _timer1_check_alarm
  clr hour

_timer1_check_alarm:
  tst alarm_active
  breq _timer1_done

  cp alarm_hour, hour
  brne _timer1_done
  cp alarm_minute, minute
  brne _timer1_done

  ; Clear alarm active bit, to prevent triggering again.
  clr intvar
  mov alarm_active, intvar

  ; Set port directly, since buzzer_activate cannot be called from interrupt.
  sbi PORTD, 7

_timer1_done:
  reti
          


Topic: Scripts and Code, by Kjetil @ 07/08-2016, Article Link