;Программа реализует простенькие электронные часы с будильником на AT90S2313.
;Кварц 10 Мгц
.cseg
.include "2313def.inc"
.org       $000
           rjmp main_program         ;Переход на основную программу
.org       $005
           rjmp clock                ;Переход на обработчик, считающий время
.org       $006
           rjmp leds_scan            ;Переход на обработчик, сканирующий
                                     ;индикатор

;Переменные для будильника
.def       alarm_hours        = r9   ;Ячейка для хранения часов
.def       alarm_minutes      = r10  ;Ячейка для хранения минут
.def       alarm_ten_hours    = r11  ;Десятки часов
.def       alarm_one_hours    = r12  ;Единицы часов
.def       alarm_ten_minutes  = r13  ;Десятки минут
.def       alarm_one_minutes  = r14  ;Единицы минут
.def       alarm_minutes_low  = r28  ;Младший байт минут
.def       alarm_minutes_high = r29  ;Старший байт минут

;Переменные для часов
.def       clock_hours        = r18  ;Ячейка для хранения часов
.def       clock_minutes      = r19  ;Ячейка для хранения минут
.def       seconds            = r21  ;Секунды
.def       clock_ten_hours    = r22  ;Десятки часов
.def       clock_one_hours    = r23  ;Единицы часов
.def       clock_ten_minutes  = r24  ;Десятки минут
.def       clock_one_minutes  = r25  ;Единицы минут
.def       clock_minutes_low  = r26  ;Младший байт минут
.def       clock_minutes_high = r27  ;Старший байт минут

;Общие переменные
.def       alarm_active       = r15  ;Признак активности режима будильника.
                                     ;Если R15=1, то часы находятся в режиме
                                     ;будильника
.def       scan_mask          = r20  ;Маска для сканирования индикатора

;Обработчик прерывания от таймера 1, выполняющий счет времени. Он вызывается
;один раз на каждую секунду
clock:     push r16
           ldi r16,high($ffff - 4883 * 2);Считать от нуля
           out tcnt1h,r16            ;до 4883 * 2
           ldi r16,low($ffff - 4883 * 2) ;Считать от нуля
           out tcnt1l,r16            ;до 4883 * 2
           push r0                   ;
           in r0,sreg                ;

           inc seconds               ;Плюс одна секунда
           cpi seconds,60            ;Прошла минута ?
           brne check_keys           ;Нет - проверим состояние клавиш

;Увеличить общий счетчик минут
           clr seconds               ;Сбросить счетчик секунд
           adiw clock_minutes_low,1  ;На одну минуту больше

           cpi clock_minutes_low,$a0 ;Закончились - ли сутки ?
           brne check_keys           ;В сутках 1440 =05A0h минут
           cpi clock_minutes_high,$05;Проверим,
           brne check_keys           ;сколько минут прошло

           clr clock_minutes_high    ;Да, сутки закончились.
           clr clock_minutes_low     ;Сбросим счетчик минут ...

;Проверка состояния клавиш
check_keys:in r16,pind               ;Получить состояние порта "D"
           ori r16,$b3               ;
           cpi r16,$bf               ;
           breq it_is_alarm          ;Нажата - ли клавиша будильника ?
           cpi r16,$fb               ;
           breq it_is_hours          ;Нажата - ли клавиша перевода часов ?
           cpi r16,$f7               ;
           breq it_is_minutes        ;Нажата клавиша перевода минут ?

;Нажаты несколько клавиш одновременно или не нажата ни одна - звонит
;будильник (если нужно).
;Здесь реализован будильник
           rcall alarm_clock
           rjmp display

it_is_hours:
           mov r16,alarm_active      ;В каком режиме находятся часы ?
           cpi r16,1                 ;Если в режиме будильника, то
           breq tune_alarm_hours     ;нужно настраивать именно будильник
;
tune_clock_hours:
           adiw clock_minutes_low,60 ;Увеличить количество минут на 60

           push clock_minutes_high   ;
           push clock_minutes_low    ;

           clr r16                   ;
new_sub_10:sbiw clock_minutes_low,60 ;Деление на
           brcs end_div_10           ;60
           inc r16                   ;методом
           rjmp new_sub_10           ;вычитания
end_div_10:cpi r16,24                ;
           brlo to_pops_1            ;

           adiw clock_minutes_low,60 ;Восстановим остаток от деления
           pop r16                   ;
           pop r16                   ;
           rjmp display              ;

to_pops_1: pop clock_minutes_low     ;
           pop clock_minutes_high    ;
           rjmp display              ;

tune_alarm_hours:
           adiw alarm_minutes_low,60 ;Увеличить количество минут на 60

           push alarm_minutes_high   ;
           push alarm_minutes_low    ;

           clr r16                   ;
new_sub_11:sbiw alarm_minutes_low,60 ;Деление на
           brcs end_div_11           ;60
           inc r16                   ;методом
           rjmp new_sub_11           ;вычитания
end_div_11:cpi r16,24                ;
           brlo to_pops_2            ;

           adiw alarm_minutes_low,60 ;Восстановим остаток от деления
           pop r16                   ;
           pop r16                   ;
           rjmp display              ;

to_pops_2: pop alarm_minutes_low     ;
           pop alarm_minutes_high    ;
           rjmp display              ;
;

it_is_minutes:
           mov r16,alarm_active      ;В каком режиме находятся часы ?
           cpi r16,1                 ;Если в режиме будильника, то
           breq tune_alarm_minutes   ;нужно настраивать именно будильник
;
tune_clock_minutes:
           adiw clock_minutes_low,1  ;Да - увеличить количество минут на одну

           cpi clock_minutes_low,$a0 ;Закончились - ли сутки ?
           brne display              ;В сутках 1440 =05A0h минут
           cpi clock_minutes_high,$05;Проверим,
           brne display              ;сколько минут прошло

           clr seconds               ;Да, сутки закончились.
           clr clock_minutes_high    ;Сбросим счетчик секунд ...
           clr clock_minutes_low     ;Сбросим счетчик минут ...
           rjmp display              ;

tune_alarm_minutes:
           adiw alarm_minutes_low,1  ;Да - увеличить количество минут на одну

           cpi alarm_minutes_low,$a0 ;Закончились - ли сутки ?
           brne display              ;В сутках 1440 =05A0h минут
           cpi alarm_minutes_high,$05;Проверим,
           brne display              ;сколько минут прошло

           clr alarm_minutes_high    ;Да, сутки закончились.
           clr alarm_minutes_low     ;Сбросим счетчик минут ...
           rjmp display              ;Отображение времени
;

it_is_alarm:
           ldi r16,$01
           eor alarm_active,r16
           in r16,portb
           push r16
           ldi r16,$ff
           out portb,r16
           mov r16,alarm_active
           cpi r16,$1
           brne pic_for_clock
pic_for_alarm:
           rcall pic_pic
           rcall delay_100ms
pic_for_clock:
           rcall pic_pic

           pop r16
           out portb,r16

display:   rcall clock_convert_to_time  ;Преобразуем минуты в обычное время
           rcall alarm_convert_to_time  ;Преобразуем показания будильника

to_reti_2: out sreg,r0               ;
           pop r0                    ;
           pop r16                   ;
           reti                      ;Выход из обработчика

;Обработчик прерывания от таймера 0, выполняющий сканирование индикатора -
;каждая цифра сканируется около 200 раз в секунду
leds_scan: push r16
           ldi r16,250               ;
           out tcnt0,r16             ;
           push r0                   ;
           in r0,sreg                ;

           in r16,portd              ;
           and r16,scan_mask         ;
           out portd,scan_mask       ;Зажечь нужный сегмент индикатора

           mov r16,alarm_active      ;Прочитать признак активности будильника

           cpi scan_mask,$fe         ;Сейчас отображаются десятки часов ?
           breq show_one_hours       ;Да
           cpi scan_mask,$fd         ;Сейчас отображаются единицы часов ?
           breq show_ten_minutes     ;Да
           cpi scan_mask,$ef         ;Сейчас отображаются десятки минут ?
           breq show_one_minutes     ;Да
           cpi scan_mask,$df         ;Сейчас отображаются единицы минут ?
           breq show_ten_hours       ;Да
           rjmp to_reti_1            ;На выход

;Отобразить единицы часов
show_one_hours:
           ldi scan_mask,$fd         ;
           cpi r16,0
           breq show_clock_1
           out portb,alarm_ten_hours ;
           rjmp to_reti_1
show_clock_1:
           out portb,clock_ten_hours ;
           rjmp to_reti_1            ;

;Отобразить десятки минут
show_ten_minutes:                    ;
           ldi scan_mask,$ef         ;
           cpi r16,0
           breq show_clock_2
           out portb,alarm_one_hours ;
           rjmp to_reti_1
show_clock_2:
           out portb,clock_one_hours ;
           rjmp to_reti_1            ;

;Отобразить единицы минут
show_one_minutes:                    ;
           ldi scan_mask,$df         ;
           cpi r16,0
           breq show_clock_3
           out portb,alarm_ten_minutes
           rjmp to_reti_1            ;
show_clock_3:
           out portb,clock_ten_minutes
           rjmp to_reti_1            ;

;Отобразить десятки часов
show_ten_hours:                      ;
           ldi scan_mask,$fe         ;
           cpi r16,0
           breq show_clock_4
           out portb,alarm_one_minutes
           rjmp to_reti_1
show_clock_4:
           out portb,clock_one_minutes

to_reti_1: out sreg,r0               ;
           pop r0                    ;
           pop r16                   ;
           reti                      ;Выход из обработчика

;Основная программа
main_program:
           sei                       ;Разрешить прерывания
           ldi r16,$df               ;Настроить начало стека
           out spl,r16               ;на конец SRAM
           ldi r16,$0                ;Включить
           mov alarm_active,r16      ;Режим часов
           ldi alarm_minutes_high,high(415);Настроить будильник
           ldi alarm_minutes_low,low(415)  ;На 6 часов 55 минут - время на WORK
           rcall ports_tuning        ;Настроить порты процессора
           rcall scanning            ;Запустить цикл сканирования индикатора
           rcall clocking            ;Запустить цикл счета времени
non_ends:  rjmp non_ends             ;Остается только крутить пустой цикл ...


;-----------------------------------------------------------------------------
;Область процедур:
;-----------------------------------------------------------------------------
;Процедура запускает цикл сканирования индикатора
scanning:  push r17                  ;Сохранить R17
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           ldi scan_mask,$ef         ;Индицировать десятки часов

           in r17,timsk              ;Разрешить прерывания
           ori r17,$02               ;по
           out timsk,r17             ;переполнению Т/С0
                                     ;
           ldi r17,$05               ;Коэффициент деления
           out tccr0,r17             ;равен 1024 + запустим таймер 0
                                     ;
           ldi r17,250               ;Прерывание
           out tcnt0,r17             ;примерно 1600 раз в секунду
                                     ;
           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r17                   ;Восстановить R17
           ret                       ;Возврат из подпрограммы

;Процедура запускает цикл счета времени
clocking:  push r17                  ;Сохранить R17
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           clr clock_minutes_high    ;Ноль минут
           clr clock_minutes_low     ;
           clr seconds               ;Ноль секунд

           ldi clock_ten_hours,$c0   ;На
           ldi clock_one_hours,$c0   ;индикаторе -
           ldi clock_ten_minutes,$c0 ;одни
           ldi clock_one_minutes,$c0 ;нули

           in r17,timsk              ;Разрешить прерывания
           ori r17,$80               ;по
           out timsk,r17             ;переполнению Т/С1
                                     ;
           ldi r17,$05               ;Коэффициент деления
           out tccr1b,r17            ;равен 1024 + запустим таймер 1
                                     ;
           ldi r17,high($ffff - 4883 * 2);Считать от нуля
           out tcnt1h,r17                ;до 4883 * 2
           ldi r17,low($ffff - 4883 * 2) ;Считать от нуля
           out tcnt1l,r17            ;до 4883 * 2
                                     ;
           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r17                   ;Восстановить R17
           ret                       ;Возврат из подпрограммы

;-----------------------------------------------------------------------------
;Процедуры для часов реального времени

;Процедура преобразует количество минут в обычное представление времени и
;помещает результат в ячейки: "clock_ten_hours", "clock_one_hours",
;"clock_ten_minutes", ""clock_one_minutes"
clock_convert_to_time:
;Разделить общее количество минут на часы и минуты
           rcall clock_minutes_to_h_and_m
           push clock_hours
           push clock_minutes
           rcall clock_hours_to_ascii;
           rcall clock_minutes_to_ascii
           pop clock_minutes
           pop clock_hours
           ret                       ;

;Процедура выделяет из общего количества минут часы и минуты. Общее количество
;минут не изменяется, сохраняясь в стеке
;Вход:  Общее количество минут в паре R27:R26
;Выход: Часы в ячейке "clock_hours" и минуты в ячейке "clock_minutes"
clock_minutes_to_h_and_m:
           push clock_minutes_high
           push clock_minutes_low
           push r0
           in r0,sreg

           ldi clock_hours,0         ;Деление
new_sub_1: sbiw r26,60               ;на
           brcs end_div_1            ;60
           inc clock_hours           ;методом
           rjmp new_sub_1            ;вычитания
end_div_1: adiw r26,60               ;Восстановим остаток от деления
           mov clock_minutes,r26     ;Остаток от деления - это минуты

           out sreg,r0
           pop r0
           pop clock_minutes_low
           pop clock_minutes_high
           ret

;Процедура преобразует двоичное значение часов в формат индикатора
;Вход:  Часы в ячейке "clock_hours"
;Выход: Ячейки "clock_Ten_hours" и "clock_One_hours"
clock_hours_to_ascii:
           push r31
           push r30
           push clock_minutes_high
           push clock_minutes_low
           push r1
           in r1,sreg
           push r0

           ldi clock_ten_hours,0     ;Деление
new_sub_2: subi clock_hours,10       ;на
           brcs end_div_2            ;10
           inc clock_ten_hours       ;методом
           rjmp new_sub_2            ;вычитания
end_div_2: ldi clock_one_hours,10    ;Второе слагаемое
           add clock_hours,clock_one_hours  ;Восстановим остаток от деления
           mov clock_one_hours,clock_hours  ;Остаток от деления - это единицы
                                            ;часов

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,clock_ten_hours
           clr r0
           adc r31,r0
           lpm
           mov clock_ten_hours,r0

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,clock_one_hours
           clr r0
           adc r31,r0
           lpm
           mov clock_one_hours,r0

           pop r0
           out sreg,r1
           pop r1
           pop clock_minutes_low
           pop clock_minutes_high
           pop r30
           pop r31
           ret

;Процедура преобразует двоичное значение минут в формат индикатора
;Вход:  Минуты в ячейке "clock_Minutes"
;Выход: Ячейки "clock_Ten_minutes" и "clock_One_minutes"
clock_minutes_to_ascii:
           push r31
           push r30
           push clock_minutes_high
           push clock_minutes_low
           push r1
           in r1,sreg
           push r0

           ldi clock_ten_minutes,0   ;Деление
new_sub_3: subi clock_minutes,10     ;на
           brcs end_div_3            ;10
           inc clock_ten_minutes     ;методом
           rjmp new_sub_3            ;вычитания
end_div_3: ldi clock_one_minutes,10  ;Второе слагаемое
           add clock_minutes,clock_one_minutes   ;Восстановим остаток от деления
           mov clock_one_minutes,clock_minutes   ;Остаток от деления - это единицы минут

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,clock_ten_minutes
           clr r0
           adc r31,r0
           lpm
           mov clock_ten_minutes,r0

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,clock_one_minutes
           clr r0
           adc r31,r0
           lpm
           mov clock_one_minutes,r0

           pop r0
           out sreg,r1
           pop r1
           pop clock_minutes_low
           pop clock_minutes_high
           pop r30
           pop r31
           ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;Процедуры для будильника

;Процедура преобразует количество минут в обычное представление времени и
;помещает результат в ячейки: "alarm_ten_hours", "alarm_one_hours",
;"alarm_ten_minutes", "alarm_one_minutes"
alarm_convert_to_time:
;Разделить общее количество минут на часы и минуты
           rcall alarm_minutes_to_h_and_m
           push alarm_hours
           push alarm_minutes
           rcall alarm_hours_to_ascii;
           rcall alarm_minutes_to_ascii
           pop alarm_minutes
           pop alarm_hours
           ret                       ;

;Процедура выделяет из общего количества минут часы и минуты. Общее количество
;минут не изменяется, сохраняясь в стеке
;Вход:  Общее количество минут в паре R29:R28
;Выход: Часы в ячейке "alarm_hours" и минуты в ячейке "alarm_Minutes"
alarm_minutes_to_h_and_m:
           push r16
           push alarm_minutes_high
           push alarm_minutes_low
           push r0
           in r0,sreg

           ldi r16,0 
           mov alarm_hours,r16       ;Деление
new_sub_4: sbiw r28,60               ;на
           brcs end_div_4            ;60
           inc alarm_hours           ;методом
           rjmp new_sub_4            ;вычитания
end_div_4: adiw r28,60               ;Восстановим остаток от деления
           mov alarm_minutes,r28     ;Остаток от деления - это минуты

           out sreg,r0
           pop r0
           pop alarm_minutes_low
           pop alarm_minutes_high
           pop r16
           ret

;Процедура преобразует двоичное значение часов в формат индикатора
;Вход:  Часы в ячейке "alarm_hours"
;Выход: Ячейки "alarm_Ten_hours" и "alarm_One_hours"
alarm_hours_to_ascii:
           push r16
           push r31
           push r30
           push alarm_minutes_high
           push alarm_minutes_low
           push r1
           in r1,sreg
           push r0

           ldi r16,0
           mov alarm_ten_hours,r16   ;Деление
new_sub_5: mov r16,alarm_hours       ;
           subi r16,10               ;на
           mov alarm_hours,r16       ;
           brcs end_div_5            ;10
           inc alarm_ten_hours       ;методом
           rjmp new_sub_5            ;вычитания
end_div_5: ldi r16,10                ;
           mov alarm_one_hours,r16   ;Второе слагаемое
           add alarm_hours,alarm_one_hours  ;Восстановим остаток от деления
           mov alarm_one_hours,alarm_hours  ;Остаток от деления - это единицы
                                            ;часов
;Конвертим значение часов в формат индикатора
           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,alarm_ten_hours
           clr r0
           adc r31,r0
           lpm
           mov alarm_ten_hours,r0

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,alarm_one_hours
           clr r0
           adc r31,r0
           lpm
           mov alarm_one_hours,r0

           pop r0
           out sreg,r1
           pop r1
           pop alarm_minutes_low
           pop alarm_minutes_high
           pop r30
           pop r31
           pop r16
           ret

;Процедура преобразует двоичное значение минут в формат индикатора
;Вход:  Минуты в ячейке "alarm_Minutes"
;Выход: Ячейки "alarm_Ten_minutes" и "alarm_One_minutes"
alarm_minutes_to_ascii:
           push r16
           push r31
           push r30
           push alarm_minutes_high
           push alarm_minutes_low
           push r1
           in r1,sreg
           push r0

           ldi r16,0
           mov alarm_ten_minutes,r16 ;Деление
new_sub_6: mov r16,alarm_minutes     ;
           subi r16,10               ;на
           mov alarm_minutes,r16     ;
           brcs end_div_6            ;10
           inc alarm_ten_minutes     ;методом
           rjmp new_sub_6            ;вычитания
end_div_6: ldi r16,10
           mov alarm_one_minutes,r16 ;Второе слагаемое
           add alarm_minutes,alarm_one_minutes   ;Восстановим остаток от деления
           mov alarm_one_minutes,alarm_minutes   ;Остаток от деления - это единицы минут

;Конвертим значение минут в формат индикатора
           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,alarm_ten_minutes
           clr r0
           adc r31,r0
           lpm
           mov alarm_ten_minutes,r0

           ldi r31,high(cross_array * 2)
           ldi r30,low(cross_array * 2)
           clc
           add r30,alarm_one_minutes
           clr r0
           adc r31,r0
           lpm
           mov alarm_one_minutes,r0

           pop r0
           out sreg,r1
           pop r1
           pop alarm_minutes_low
           pop alarm_minutes_high
           pop r30
           pop r31
           pop r16
           ret
;-----------------------------------------------------------------------------

;Процедура настраивает порты при включении часов
ports_tuning:
           ldi r16,$ff               ;Настроить порт PB
           out ddrb,r16              ;на вывод данных
           ldi r16,$b3               ;Настроить порт PD на вывод, кроме
           out ddrd,r16              ;выводов PD2, PD3 и PD6
           sbi portd,6               ;Использовать внутренние PULLUP's
           sbi portd,3               ;для выводов
           sbi portd,2               ;PD2, PD3 и PD6
           ret

;Процедура генерирует звуковой сигнал будильника
pic_pic:   push r16                  ;Сохранить R16
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           ldi r16,160
ring_1:    cbi portb,7
           rcall delay_1ms
           sbi portb,7
           rcall delay_1ms
           dec r16
           brne ring_1

           ldi r16,255
ring_2:    cbi portb,7
           rcall delay_05ms
           sbi portb,7
           rcall delay_05ms
           dec r16
           brne ring_2

           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r16                   ;Восстановить R16
           ret

;Процедура формирует задержку - примерно 100 миллисекунд
delay_100ms:
           push r16                  ;Сохранить R16
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           ldi r16,100
l_5:       rcall delay_1ms
           dec r16
           brne l_5

           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r16                   ;Восстановить R16
           ret

;Процедура формирует задержку - примерно 1 миллисекунду
delay_1ms: push r0                   ;Сохранить
           in r0,sreg                ;SREG

           rcall delay_05ms          ;Два вызова
           rcall delay_05ms          ;следующей процедуры

           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           ret

;Процедура формирует задержку - примерно 0,5 миллисекунд
delay_05ms:push r16                  ;Сохранить R16
           push r17                  ;Сохранить R17
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           ldi r17,$6
l_4:       ldi r16,$160
l_3:       nop
           dec r16
           brne l_3
           dec r17
           brne l_4

           out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r17                   ;Восстановить R17
           pop r16                   ;Восстановить R16
           ret

alarm_clock:
           push r16                  ;Сохранить R16
           push r0                   ;Сохранить
           in r0,sreg                ;SREG

           cp clock_hours,alarm_hours
           brne to_ret
           cp clock_minutes,alarm_minutes
           brne to_ret
           in r16,portb
           push r16
           ldi r16,$ff
           out portb,r16
           rcall pic_pic
           pop r16
           out portb,r16

to_ret:    out sreg,r0               ;Восстановить
           pop r0                    ;SREG
           pop r16                   ;Восстановить R16
           ret

;*****************************************************************************
;Изображения цифр на индикаторе
cross_array:   .db   $c0, $f9, $a4, $b0, $99, $92, $82, $f8, $80, $90, $7f