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