	list      p=16f628A           ; Тип процессора
	#include <p16F628A.inc>       ; Файл определений для PIC16F628A

	errorlevel  -302              ; Отключаем предупреждения компилятора о банках памяти (ну мешают они)

	__CONFIG   _CP_OFF & _DATA_CP_OFF & _LVP_OFF & _BOREN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC		;Слово кофигурации


;********* Описание переменных
w_temp        EQU     0x71        ; Переменная для сохранения аккумулятора (W) в прерывании
status_temp   EQU     0x72        ; Тоже для сохранения регистра STATUS

	CBLOCK	0x20
		digit1
		digit2
		digit3
		digit4
		blink1
		blink2
		blink3
		blink4
		tics, blink_tics
		blink_flags
		hours, mins, secs
		bin, hund, t_a_o
		tmp_out
		k_tmr
		z_tmr
		z_tmr_set
		keys
		key_flags
	ENDC



;**********************************************************************
	ORG     0x000             ; Верктор сброса (PIC запусается от сюда)
	movlw	0x07
	movwf	CMCON			  ; Отключаем компараторы и идем
	goto    Init              ; на инициализацию
	

	ORG     0x004             ; Вход в прерывание
	movwf   w_temp            ; Сохранение аккумулятора W
	movf	STATUS,w          ; 
	movwf	status_temp       ; Сохранение регистра STATUS
	clrf	STATUS			  ; Сброс регистра STATUS, чтобы быть уверенным, что мы в нулевом банке
	bcf		PIR1,TMR2IF		  ; Сброс флага таймерного прерывания

ind_start
	movlw	b'11110000'		  ;Выключаем все знакоместа индикатора
	andwf	PORTA,f
	comf	INDF,W			  ;Получаем цифру из косвенной ячейки с инверсией (т.к. индикатор с общим анодом)
	movwf	tmp_out			  ;Сохраняем ее в временной переменной

	movlw	d'4'			  ;Увеличиваем косвенный указатель на 4
	addwf	FSR,f
	movf	INDF,W			  ;Получаем значение маски мигания
	btfsc	blink_flags,0	  ;и если в данном случае нужно погасить сегмент (флаг установлен)
	iorwf	tmp_out,f		  ;то заменяем сегменты из маски на лог.1 (гашение сегмента)
	movlw	d'4'			  ;Уменьшаем назад на 4 косвенный указатель.
	subwf	FSR,f

	movf	tmp_out,W		  ;Выводим значение временной переменной
	movwf	PORTB			  ;в порт В

	movlw	0x20			
	subwf	FSR,W			  ;Уменьшаем указатель косвенной адресации на H'20' (т.е. результат будет от 0 до 3, т.к. 4 знака)
	call	ind_cnv			  ;И идем на таблицу конвертирования (выборки) нужного знакоместа
	iorwf	PORTA,f			  ;Выводим выбранное знакоместо в ПОРТ А (все, цифра светиться)

	incf	FSR,f			  ;Инкрементируем косвенный указатель
	movlw	0x20+d'4'		  ;Проверяем все ли 4 цифры отображены на экране
	subwf	FSR,W		
	btfss	STATUS,Z		
	goto	clock			  ;Если нет, то идем на обработку часов
	movlw	0x20			  ;если да, то ставим FSR в исходное состояние - на адрес 1-й цифры
	movwf	FSR
	goto	clock

ind_cnv	
	andlw	b'00000011'		  ;Таблица выборки знакоместа
	addwf	PCL,f
	retlw	b'00000010'	;0	  ;1-знак на RA1
	retlw	b'00000100'	;1	  ;2-знак на RA2
	retlw	b'00000001'	;2	  ;3-знак на RA0
	retlw	b'00001000'	;3	  ;4-знак на RA3


clock	
	incf	tics,f			  ;Инкрементируем тики
	movlw	d'250'		
	subwf	tics,W			  ;Если тики не дошли до 250
	btfss	STATUS,Z		  ;
	goto	skip_clock		  ;то пропускаем обработку часов
	clrf	tics			  ;А если дошли до 250 то сбрасуем их

	incf	secs,f			  ;и инкрементируем секунду
	movlw	d'60'			  
	subwf	secs,W			  ;Тут мы проверяем дошел ли счетчик секунд до 60
	btfss	STATUS,Z		  
	goto	skip_clock		  ;если нет, то пропускаем дальнейшую обработку часов
	clrf	secs			  ;а если да, то сбрасываем секунды
	
	incf	mins,f			  ;Также после 60 сек - инкрементируем минуты
	movlw	d'60'			
	subwf	mins,W			  ;и также контролируем переполнение 60 минут
	btfss	STATUS,Z
	goto	skip_clock
	clrf	mins

	incf	hours,f			  ;И собственно инкрементируем часы
	movlw	d'24'
	subwf	hours,W			  ;Только переполнение будет при 24 часах.
	btfss	STATUS,Z
	goto	skip_clock	
	clrf	hours			  ;Далее можно наращивать, ввести счетчик дней, месяцев, лет, веков и т.д. :-)

skip_clock
	incf	blink_tics,f   		;Как и в часах инкрементируем счетчик тиков для мигания.
	movlw	d'125'				;Каждые полсекунды (секунда 250, а пол.секунды 125) тиков
	subwf	blink_tics,W		
	btfss	STATUS,Z
	goto	skip_blink
	clrf	blink_tics

	movlw	b'00000001'			;Инвертируем нулевой флажок (он то и служит для мигания)
	xorwf	blink_flags,f	

skip_blink
go_home
	movf    status_temp,w     ; Восстанавливаем регистр STATUS
	movwf	STATUS            ; 
	swapf   w_temp,f
	swapf   w_temp,w          ; Восстанавливаем аккумулятор W
	retfie                    ; Выходим из прерывания


;********************************************************************************************* Табличная конвертация
convert
	andlw	0x0F			;Маскируем ненужные биты (чтобы случайно не выскочить за предел таблицы)
	addwf	PCL,f			;Прибавляем значение аккумулятора к счетчику команд PCL. Тем самым выполняя смешение в таблице
			 ;BAEFhCDG		;В соответствии со схемой определяем к какой ножке порта В какой сегмент подключен.
	retlw	b'11110110'		;0
	retlw	b'10000100'		;1
	retlw	b'11100011'		;2
	retlw	b'11000111'		;3
	retlw	b'10010101'		;4
	retlw	b'01010111'		;5
	retlw	b'01110111'		;6
	retlw	b'11010100'		;7
	retlw	b'11110111'		;8
	retlw	b'11010111'		;9
	retlw	b'00000000'		;A
	retlw	b'00000000'		;B
	retlw	b'00000000'		;C
	retlw	b'00000000'		;D
	retlw	b'00000000'		;E
	retlw	b'00000000'		;F

;********************************************************************************************* Основной цикл
main
	call	show_clock		;Идем на подпрограмму отображения часов
	call	key_test		;Идем на подпрограмму опроса кнопок
	clrwdt					;Сбрасуем сторожевой таймер
	goto	main			;и повторяем все с начала

;****************************************************************************************************** Опрос кнопок
key_test
	incf	k_tmr,f			;Инкрементируем временный счетчик
	movlw	0x7F
	subwf	k_tmr,W			;Если значение временного счетчика дошло до H'7F'
	btfsc	STATUS,Z	
	goto	read_1			;То выполняем 1-й опрос кнопок
	movlw	0xFF
	subwf	k_tmr,W			;Если значение счетчика FF
	btfsc	STATUS,Z
	goto	read_2			;То выполняем 2-й опрос (это сделано для защиты от дребезга контактов)
	return					;При остальных значениях счетчика ничего не делаем.

read_1						;1-й опрос
	movf	PORTA,W			;Получаем данные с ПОРТА А
	andlw	b'00110000'		;Маскируем ненужные биты
	movwf	keys			;Сохраяем значение в переменной keys
	return			

read_2						;2-й опрос
	movf	PORTA,W			;Еще раз получаем данные с ПОРТА А
	andlw	b'00110000'		;маскируем не нужные биты
	xorwf	keys,W			;и сравнивам значение с предыдущим
	btfss	STATUS,Z		;Если значения разные - то выходим никак их не обрабатывая (дребезг - чтение не удачное)
	return					
	
	incf	z_tmr,f			;Инкрементируем еще один защитный счетчик
	movf	z_tmr_set,W
	subwf	z_tmr,W		
	btfss	STATUS,C		;Чтобы при удержании кнопки цифры не бежали очень быстро
	return
	clrf	z_tmr

	movlw	0x06			;Сразу ставим для z_tmr время быстрого изменения (когда удерживается кнопка)
	movwf	z_tmr_set

	
	btfss	keys,4			;Проверяем состояние кнопки №1
	goto	inc_hour
	btfss	keys,5			;Проверяем состояние кнопки №2
	goto	inc_min

	movlw	0x01		;Если кнопки никакие не нажимались, то Z_TMR ставим на минимум (для быстрой реакции на кнопку)
	movwf	z_tmr_set
	clrf	key_flags	;И сбрасуем флаги ранее нажатых кнопок (т.к. сюда управление попадает только когда обе кнопки не нажаты)
 
	return

inc_hour
	movf	key_flags,W		;Проверяем кнопка была ли ранее нажата, т.е. стоит ли уже ее флажок
	btfss	STATUS,Z
	goto	skip_hour_fast	;Если да, то идем на икремент часов (удерживается кнопка)
	movlw	0x0D
	movwf	z_tmr_set		;А если первое нажатие, то устанавливаем более длинную задержку


skip_hour_fast
	bsf		key_flags,0		;Ставим флажок нажатой кнопки
	clrf	secs			;Сразу сбрасуем секунды
	incf	hours,f			;Инкрементируем счетчик часов
	movlw	d'24'			;Проверяем на переполнение 
	subwf	hours,W
	btfsc	STATUS,Z
	clrf	hours
	return
	

inc_min						
	movf	key_flags,W		;Тут как и выше, проверяем удерживается кнопка или только нажата
	btfss	STATUS,Z		
	goto	skip_min_fast	;И идем на икремент минут

	movlw	0x0D			;Либо добавляем более длинную задержку
	movwf	z_tmr_set

skip_min_fast
	bsf		key_flags,0		;Ставим флажок нажатой кнопки
	clrf	secs			;Сбрасуем секунды 
	incf	mins,f			;инкрементирум счетчик минут
	movlw	d'60'			;проверяя на переполнение
	subwf	mins,W
	btfsc	STATUS,Z	;Секунды сбрасуем по следующей причине. При увеличении минуты(или часа) на 1 и вдруг
	clrf	mins		;в этот момент будет сгенерировано таймерное прерывания, то минуты могут перескочить 
	return			;с 59 не в 00, а в 61, т.е. нарушиться нормальная работа программы.
				;Поэтому простейший выход из этой ситуации обнулять секунды.
				;Вероятность этого события ничтожно мала. Но как известно, если какая нибудь неприятность
				;может случиться, то она случается :-)
	
;*************************************************************************************************** Отображаем часы
show_clock
		 
	clrf	blink1			;в 1-м знакоместе ничего не должно мигать, сбрасуем маску
		     ;BAEFhCDG
	movlw	b'00001000'		;во 2-м знакоместе должно мигать двоеточие, ставим в маске бит h
	movwf	blink2		
	clrf	blink3			;3 и 4 знакоместа тоже не мигают, сбрасуем маску
	clrf	blink4

	movf	hours,W		;получаем значение часов
	movwf	bin			;в временную переменную
	call	b_bcd		;и выполняем двоично-десятичную коррекцию

	swapf	t_a_o,W		;Получаем десятки часов (уже скорректированных) и Выделяем только правый полубайт.
	andlw	0x0F		;(вобще десятки часов в левом полубайте, но командой выше мы их поменяли местами)
	btfsc	STATUS,Z	;Проверяем если десятки равны нулю - то место их значения
	movlw	0x0A		;Загружаем код 0A что в конвертирующей таблице соответствует пустому знакоместу. Т.е. гасим незначащий ноль
	call	convert		;Конвертируем их в набор битов для управления сегментами (дешефрируем)           
	movwf	digit1		;и помещаем в первое знакоместо

	movf	t_a_o,W		;тоже делаем с единицами часов
	andlw	0x0F
	call	convert
		     ;BAEFhCDG
;	iorlw	b'00001000'	;Добавляем разделительную точку (двоеточие)
	movwf	digit2


	movf	mins,W		;Теперь делаем десятичную коррекцию минут
	movwf	bin
	call	b_bcd

	swapf	t_a_o,W		;Также как и выше, меняем полубайты - получаем десятки
	andlw	0x0F		;Гасить ноль здесь не надо.
	call	convert
	iorlw	b'00001000'	;Добавляем разделительную точку (двоеточие)
	movwf	digit3		;и выводим соотвественно в 3 и 4 занкоместа значение десяткой и единиц минут.

	movf	t_a_o,W		;Единицы аналогично, без перемены местами полубайтов
	andlw	0x0F
	call	convert
	movwf	digit4		
	return				;Все данные для индикации готовы. Прерывание будет постоянно выполнять динамическую индикацию.

;***********************************************************Подпрограмма преобразования двоичного числа в двоично-десятичное
b_bcd
	clrf	hund				;Используем эту подпрограмму как есть, не вникая в ее тонкости
	swapf	bin,W
	addwf	bin,W				;тут важно то, что переменная BIN будет преобразована
	andlw	b'00001111'			;в двоично-десятичный формат.
	skpndc					;Пример 0x0F - это 15. 
	addlw	0x16				;bin = 0x0F
	skpndc					;hund (сотни) = 0x00
	addlw	0x06				;t_a_o (десятки и единицы) = 0x15
	addlw	0x06				;т.е. в t_a_o старший и младщий полубайты содержат десятичное значение
	skpdc					;десятков и единиц соответственно.
	addlw	-0x06
	btfsc	bin,4
	addlw	0x16-1+0x6
	skpdc
	addlw	-0x06
	btfsc	bin,5
	addlw	0x30
	btfsc	bin,6
	addlw	0x60
	btfsc	bin,7
	addlw	0x20
	addlw	0x60
	rlf		hund,f
	btfss 	hund,0
	addlw 	-0x60
	movwf 	t_a_o
	btfsc 	bin,7
	incf	hund,f
	return
	
;***************************************************************************************************** ИНИЦИАЛИЗАЦИЯ
Init
	clrwdt					;Сбрасуем сторожевой таймер
	movlw	0xFF			;Гасим все в ПОРТУ В (инверсно, т.к. индикатор с общим анодом и 1 - это выключено)
	movwf	PORTB			;Хотя пока у нас порт еще в Z состоянии, но мы сразу подготавливаем выходную защелку
	clrf	PORTA			;Очищаем и защелку ПОРТА А

	bsf		STATUS,RP0		;Переходим в 1-й банк
	movlw	b'11110000'		;Настраиваем RA0-RA3 на вывод RA4-R5 на ввод (RA6 RA7 не используются)
	movwf	TRISA
	movlw	b'00000000'		;Настраиваем весь порт В на вывод
	movwf	TRISB
	movlw	b'10000000'		;Настройка опций. Отключаем подтягивающие резистроы. Остальное в данном девайсе не имеет значения
	movwf	OPTION_REG
	movlw	d'249'			;Устанавливаем период TMR2 - 249 (получится что таймер будет переполняться каждые 4 мс)
	movwf	PR2
	bsf		PIE1,TMR2IE		;Разрешаем прерывания от TMR2
	bcf		STATUS,RP0		;Возвращаемся в нулевой банк

	movlw	b'00000111'		;Настраиваем предделитель TMR2 1:16 и включаем таймер
	movwf	T2CON

	movlw	0x20			;Устанавливаем косвенный указатель на адерс 0х20 (1-й знак индикатора)
	movwf	FSR

	clrf	digit1			;Очищаем все нужные нам переменные.
	clrf	digit2
	clrf	digit3
	clrf	digit4
	clrf	blink1
	clrf	blink2
	clrf	blink3
	clrf	blink4
	clrf	tics
	clrf	blink_tics
	clrf	blink_flags
	clrf	hours
	clrf	mins
	clrf	secs
	clrf	k_tmr
	clrf	z_tmr
	clrf	z_tmr_set
	clrf	keys
	clrf	key_flags

	clrwdt				;Сбрасуем сторожевой таймер
	
	bsf		INTCON,PEIE		;Разрешаем прерывания от перефирийных модулей (в нашем случаем ТМР2)
	bsf		INTCON,GIE		;И разрешаем глобально прерывания - все, теперь будут работать прерывания
	goto	main			;А мы идем на метку MAIN в основной цикл



	END                       ; Деректива - КОНЕЦ ПРОГИ

