PIClist RUS
микроконтроллеры PIC и интерфейсы
техническая документация
статьи и разработки на русском языке

Секундный таймер с нулевой погрешностью

« назад на главную страницу

Оригинал: Roman Black, "Zero-error One Second Timer!"

Скачать "Секундный таймер с нулевой погрешностью" в формате PDF

Перевод с английского © PIClist-RUS (piclist.ru)


Введение

Простая и быстрая система получения точных временных периодов на микроконтроллере PIC.

Эта система (ассемблерный код для PIC прилагается) даёт простой, быстрый способ генерировать достоверные периоды на микроконтроллере PIC с любой тактовой частотой. Особенно для односекундных событий, таких как простые часы. Вы можете использовать любой кварц, какой у вас есть: 4.0 МГц, 12.137253 МГц; ЛЮБОЙ кварц и ЛЮБОЕ значение предделителя, и всё равно будете получать идеальную односекундную синхронизацию.

Система будет генерировать достоверные периоды от миллисекунд до многих секунд при очень быстром исполнении кода.

Замечание! Эта система может иметь колебания, то есть каждый период может быть частично дольше или короче, но полное функционирование таймера даёт нулевую погрешность, идеальную для часов и регистраторов данных. Процент дребезжания можно легко минимизировать, если это важно для вас.

Теория

Здесь используется модифицированная версия алгоритма Брезенхама, который сначала использовался для обеспечения быстрой обработки движения пера в перьевых графопостроителях.

Система Брезенхама идеальна для пошагового перемещения на точное расстояние с нулевой погрешностью (однако при этом важно понимать, что каждый шаг будет немного длиннее или короче).

Представьте, что вы можете двигаться только по "клеточкам" сетки, то есть вы можете сдвинуться только на 3 или на 4 клеточки, но в целом вам нужно сдвигаться на 3.37629 клеточки при каждом движении. Всё, что вы делаете - это сдвигаетесь иногда на 3, а иногда на 4 клеточки. Система обеспечивает итоговую нулевую погрешность и всегда выбирает ближайшую "клеточку" (или 3 или 4) для этого движения.

Красота системы в том, что вы можете использовать любую частоту и любую длину периода. И всё равно будете получать периоды с нулевой погрешностью.

Вместо того чтобы объяснять алгоритм Брезенхама и его многочисленные вариации, обратимся к конкретному примеру.

Процедура

Пример

PIC тактовой с частотой 4 МГц = 1 000 000 отсчётов в секунду.
Прерывание каждые 256 отсчётов.
Мы хотим сгенерировать 1-секундное событие.
(24-битная переменная требует три регистра PIC-микроконтроллера.)

Процедура

- При загрузке мы добавляем 1 000 000 в вашу 24-битную переменную.
- Прерывание происходит каждые 256 отсчётов.
- При каждом прерывании мы вычитаем 256 из нашей 24-битной переменной.
- Когда значение переменной становится меньше нуля, мы генерируем событие и снова прибавляем 1 000 000 к переменной.

Это довольно просто - когда переменная становится меньше нуля, она содержит погрешность, таким образом, ПРИБАВЛЕНИЕ 1 000 000 к ней удаляет эту ошибку из следующей секунды, давая нулевую погрешность для любого более длительного периода времени.

Замечание по колебанию! Максимальное отклонение - это число, которое мы вычитаем при каждом прерывании, таким образом, в данном случае максимальная погрешность - 256 отсчётов или 0.0256%. Это около 1/40 максимума процента ошибки для каждой секунды, полная же ошибка для длительного срока равна НУЛЮ.

Улучшенная процедура

Улучшения, которые я произвёл в моём проекте, базируются на аппаратных особенностях микроконтроллера PIC. Очень легко вычитать 256 из 24-битной переменной - вы просто вычитаете "1" из среднего байта переменной, и если знак получился отрицательный, вычитаем "1" из старшего байта.

Эта операция эквивалентна обычному 24-х битному вычитанию, а реализуется всего несколькими командами.

Другое улучшение связано с перемещением точки "события" от генерации события при "меньше нуля" к при "меньше 256". Когда старший и средний байты равны нулю, наступает событие "меньше 256".

Нам не нужно постоянно заботится о младшем байте и обрабатывать отрицательные значения.

Также было исправлено "прибавление 1 000 000", потому что у нас теперь нет отрицательных значений, и старший и средний байты будут всегда равны нулю, когда мы выполняем прибавление. Нам только нужно прибавить МЛАДШИЙ байт, проверить на перенос в средний байт, затем просто загрузить средний и старший байты (это намного быстрее, чем обычное 24-битное сложение).

Пример

PIC тактовой с частотой 4 МГц = 1 000 000 отсчётов в секунду.
Прерывание каждые 256 отсчётов.
Мы хотим сгенерировать 1-секундное событие.
(24-битная переменная требует три регистра PIC-микроконтроллера.)

Процедура

- При загрузке мы добавляем 1 000 000 + 256 в нашу 24-битную переменную.
- Прерывание происходит каждые 256 отсчётов.
- При каждом прерывании мы декрементируем наш средний байт (вычитаем 256)
- Проверяем на знак и декрементируем старший байт, если нужно.
- Когда старший и младший байты оба равны нулю, мы генерируем событие и прибавляем 1 000 000 в 24-битную переменную.

Помните, что "прибавить 1 000 000" теперь очень просто - мы просто загружаем старший и средний байты, затем прибавляем младший байт и, если он переполняется, инкрементируем средний байт.

Пример кода

Без использования прерывания

Этот пример подходит для любого PIC - он будет работать с 12-разрядными устройствами, подобными 12C508, с 14-разрядными устройствами, подобными 16F84 и т.д.

Он не требует какого-либо прерывания или вызовов, поскольку стек не используется.

Он генерирует период в одну секунду при тактовой частоте PIC 4 МГц, бит 7 таймера TMR0 используется как фиктивное "прерывание".

С использованием прерывания

Этот пример подходит для микроконтроллеров PIC с прерыванием по переполнению таймера TMR0. Односекундный таймер в данном случае автоматический и не требует опроса.

Он генерирует период в одну секунду при тактовой частоте PIC 4 МГц, прерывание таймера TMR0 устанавливается на возникновение каждые 256 команд.

Улучшения и предложения

Оба примера кода, приведённые выше, имеют частоту возникновения "прерывания" каждые 256 команд. Это сильно упрощает прибавление и вычитание и делает их очень быстрыми.

Для приложений, где вы не возражаете против немного большего колебания, вы можете делать прерывания каждые 65536 (256х256) команд, это будет также просто и быстро, вам только нужно будет делать проверку каждые 65536 отсчётов, что оставляет больше времени для другого вашего кода. Итоговая ошибка останется равной нулю, но колебание в каждой отдельной секунде увеличится. Максимальное колебание составит около 0.066 секунды или около 7 %. Все равно это даёт точный отсчёт времени, так как никого не волнует, что секунда выдаётся на 7 % раньше или позднее, и в итоге часы обеспечивают точное время благодаря тому, что итоговая ошибка равна нулю.

Как заявлено выше, эта система будет работать с любой тактовой частотой и любой длиной периода, однако, если ваше прерывание не происходит с множителем 256, вам нужно будет использовать немного более длинное 24-битное сложение и вычитание, хотя вы можете оптимизировать код сложения и вычитания до более быстрого, чем обычное.

Примеры кода снабжены комментариями и могут быть легко адаптированы под ваши нужды.

Окончательные примеры

Пример 1

Кварц в 4.43 МГц можно найти почти в каждом старом телевизоре или видеомагнитофоне. Он отлично подходит для "домашних" проектов, где нужна синхронизация, и если вы не хотите покупать другой нормальный кварц.

4.43 МГц / 4 = 1 107 500 команд в секунду.

Просто используйте коды примеров, приведённые выше, и загрузите в 24-битную переменную значение 1 107 500 = 10 E6 2C (в шестнадцатеричном формате). Это даст односекундное событие.

Конечно, вы можете использовать любой высокоскоростной кварц и просто менять добавляемое значение.

Пример 2

Сгенерировать 1200 Гц при кварце 16 МГц.

Это может быть полезным для PIC приложений, в которых нужна реализация последовательного протокола связи с заданной скоростью передачи (в нашем случае 1200 бод).

16 МГц / 4 = 4 000 000 команд в секунду

4 000 000 / 1200 = 3333.3 отсчётов в секунду.

Просто используйте код примеров, приведённых выше, и загрузите в 24-битную переменную значение 3333 = 00 0D 05 (в шестнадцатеричном формате).

На больших частотах могут возникнуть проблемы с колебаниями, максимальное колебание будет около 256 отсчётов, которое составляет около 8 % на частоте 1200 Гц. Это ещё довольно-таки неплохо.

Заключение

Недостатки

- Эта система добавляет небольшой сдвиг каждому периоду (плавает продолжительность каждого периода)

Достоинства

- Нулевая итоговая погрешность, идеальная для часов и регистраторов данных.
- Очень быстрое выполнение
- Может быть добавлена в существующую разработку с любой частотой кварца.
- Даёт фактически любой период на любом кристалле.
- Периоды могут быть изменены программно и всё равно останутся точными.
- Может быть добавлена в ваше существующее прерывание.
- Прекрасно подходит для дешёвых PIC
- В большинстве (односекундных) случаев колебание очень маленькое, меньше 0.05 %.

Исходный код программы без прерываний


;******************************************************************************
; СЕКУНДНЫЙ ТАЙМЕР С НУЛЕВОЙ ПОГРЕШНОСТЬЮ
; ВЕРСИЯ БЕЗ ПРЕРЫВАНИЙ
;
; для PIC 16F84 с частотой 4 МГц (или большинства PIC)
; (этот код был скомпилирован в MPLAB и аппаратно протестирован)
;
; Генерирует событие каждую секунду (или другой период) для любого PIC
; с любой тактовой частотой.
;
; Эта версия использует "фиктивное" прерывание через каждые 256 команд.
; Код может быть адаптирован для различных тактовых частот, длин периодов
; и уровней точности.
;******************************************************************************

;==============================================================================
include <p16f84.inc>		; определение процессора
;==============================================================================
LIST b=5, n=97, t=ON, st=OFF	; настройки MPLAB
				; абсолютная табуляция кода=5, строк=97,
				; обрезать длинные строки=ON, таблица перекодировки символов=OFF

__CONFIG  _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
				; настройки для 16F84; защита кода OFF,
				; сторожевой таймер OFF, таймер при включении питания ON,
				; тип генератора - XT (для кварца или керамического резонатора
				; частотой 4 МГц).
;==============================================================================
			; переменные
CBLOCK 0x20		; начало ОЗУ в 16F84
bres_hi			; старший байт нашей 24-битной переменной
bres_mid		; средний байт
bres_lo			; младший байт
			; (нам нужно только 3 байта для этой системы)
ENDC
;==============================================================================
			; код
org 0x000	 	; установка начала памяти программ на вектор прерывания
reset
goto setup		; настроить прерывания и порты
			; Замечание! В этом коде прерывания не используются.
;==============================================================================

;******************************************************************************
; НАСТРОЙКА     (выполняется только один раз при запуске)
; Замечание! версия для 16F84. здесь мы настраиваем периферию и направления портов.
; Для разных PIC этот код нужно будет менять соответственным образом.
;******************************************************************************
;------------------
setup			; метка для перехода
;------------------
			; настройка регистра OPTION
movlw b'10000000'	
     ;  x-------	; 7, 7, 0=включить, 1=отключить, подтяжка порта portb
     ;  -x------	; 6, 1=/, бит выбора фронта прерывания
     ;  --x-----	; 5, источник таймера timer0, 0=внутренний генератор, 1=внешний вывод.
     ;  ---x----	; 4, внешний фронт timer0, 1=\
     ;  ----x---	; 3, привязка предделителя, 1=wdt, 0=timer0
     ;  -----x--	; 2,1,0, значение предделителя таймера timer0
     ;  ------x-	;   000=2, 001=4, 010=8, 011=16, и т.д.
     ;  -------x	; 
			; Замечание! Мы устанавливаем предделитель таймера timer0 в 2,
			; это поясняется позже, в MAIN.
				
banksel OPTION_REG	; выбрать надлежащий банк регистров
movwf OPTION_REG	; загрузить данные в OPTION_REG
banksel 0		; вернуться к нормальному банку регистров bank 0
;-------------------------------------------------
			; установка направлений выводов порта PORTB
			; 1=вход, 0=выход
clrf PORTB	

movlw b'00000000'	; все 8 выводов порта portb устанавливаем как выходы
banksel TRISB		; выбрать надлежащий банк регистров
movwf TRISB		; установить маску на порт portb
banksel 0		; вернуться к нормальному банку регистров
;-------------------------------------------------
			; установка направлений выводов порта PORTA
			; 1=вход, 0=выход
clrf PORTA	
movlw b'00000000'	; все 5 порта porta устанавливаем как выходы,
			; (в 16F84 порт porta имеет только младшие 5 бит)
banksel TRISB		; выбрать надлежащий банк регистров
movwf TRISA		; установить маску на порт porta
banksel 0		; вернуться к нормальному банку регистров
;-------------------------------------------------
; настройка регистра INTCON
; в этом примере кода всё, что нам нужно - 
; убедиться, что все прерывания отключены.

movlw b'00000000'	; GIE=off TOIE=off (прерывание по переполнению таймера timer0)
movwf INTCON		; настроить прерывания.

;-------------------------------------------------
; Замечание! Теперь аппаратная часть настроена и нам нужно загрузить
; первый отсчёт (count) для одной секунды в нашу 24-битную переменную.
;-------------------------------------------------
			; Замечание! Этот пример использует тактовую частоту 4 МГц, которой
			; соответствует 1,000,000 отсчётов в секунду.
			; Нам нужен период в 1 секунду, поэтому мы должны загружать
			; 1,000,000 отсчётов каждый раз.
			; 1,000,000 = 0F 42 40 (в шестнадцатеричном формате)
			; Нам также нужно добавить 256 отсчётов для первого раза,
			; таким образом мы просто прибавляем 1 к среднему байту.
			; Если нужно, проверить переполнение среднего байта.

			; здесь мы загружаем 24-битную переменную.
movlw 0x0F		; получить значение старшего байта
movwf bres_hi		; загрузить в старший байт
movlw 0x42 +1		; получить значение среднего байта (заметьте, что мы прибавили к нему 1)
movwf bres_mid		; загрузить в средний байт
movlw 0x40		; получить значение младшего байта
movwf bres_lo		; загрузить в младший байт

			; теперь установка завершена, мы можем начать исполнение.
;-------------------------------------------------
goto main		; запустить главный цикл программы
;------------------------------------------------------------------------------

;******************************************************************************
;  MAIN     (главный цикл программы)
;******************************************************************************
;------------------
main			; метка для перехода
;------------------

;-------------------------------------------------
; Замечание! Этот пример не использует прерываний.
; Он может быть очень полезным, если вы используете PIC младших семейств, таких как 12c508,
; или в разработке, где прерывания могли бы быть проблемой, но при этом
; вам нужны события с достоверным периодом.
;
; Однако, мы должны опрашивать "фиктивное" прерывание и вручную
; проверять timer0. Эта система довольно быстра, поскольку не занимает
; слишком большого интервала.
;
; "Фиктивное" прерывание выполняется опросом бита 7 таймера timer0,
; предделитель которого настроен как 2:1.
; Это означает, что бит7 устанавливается после каждых 256 команд, и остаётся
; установленным в течение безопасного периода в 256 команд. В течение последних
; 256 команд мы ДОЛЖНЫ опросить и обнаружить его.
;
; Таким образом, максимальная задержка между опросами - от 256 до 512 команд.
; Предлагаю безопасное значение в 250 команд между опросами.
;
; Убедитесь, что код вашей основной программы никогда не выполняет более
; 250 команд перед следующим опросом "фиктивного" прерывания.
;-------------------------------------------------
main_loop		 
			; Замечание! здесь располагается ваш основной программный код,
			; или вызовы к частям основной программы.
			; Помните, ничто не должно занимать более чем
			; 250 команд всего, или мы можем пропустить
			; "фиктивное" прерывание.
; код
; код
; код
; код

;-------------------------------------------------
; Опрос (проверка) нашего "фиктовного" прерывания!
;-------------------------------------------------
			; всё, что мы делаем, - это проверка, установлен ли бит 7 таймера,
			; если да - тогда генерируем "фиктивное" прерывание.
			;
btfss TMR0,7		; проверка, установлен ли бит 7
goto main_loop		; сброшен, фиктивное прерывание не произошло, продолжаем цикл.
			; установлен, мы достигли 256 отсчётов, прошедших с последнего
			; "фиктивного" прерывания.

;-------------------------------------------------
; Если мы попали сюда, значит, было обнаружено "фиктивное" прерывание!
;-------------------------------------------------
; Замечание! Это будет происходить каждые 256 команд, 
; теперь мы можем выполнять нашу специальную систему односекундного таймера.
; Она включает три основных шага:
; * вычесть 256 из нашей 24-битной переменной
; * проверить, достигнута ли контрольная точка
; * если да, прибавить 1,000,000 к 24-битной переменной и сгенерировать событие.
;-------------------------------------------------

bcf TMR0,7		; сначала сбросить бит 7 таймера timer0
			; это никогда не сбросит таймер, так что он останется точным,
			; поскольку он по прежнему содержит млпдшие биты
			; и сохранит точное время.
;-------------------------------------------------
			; * здесь используется оптимизированное 24-битное вычитание 
			; Оно сделано с минимумом команд.
			; Мы вычитаем 256 из 24-битной переменной,
			; просто декрементируя средний байт.

tstf bres_mid		; сперва проверим на mid==0
skpnz			; nz = нет потери значимости
decf bres_hi,f		; z, потеря значимости - тогда декрементируем самый старший байт

decfsz bres_mid,f	; декрементируем средний байт (вычитаем 256)
			; теперь полное 24-битное оптимизированное вычитание выполнено!
			; это примерно в 4 раза быстрее, чем "должное" 24-битное вычитание

goto int_exit		; nz, значит, секунда ещё не прошла.
			; в большинстве случаев полностью "фиктивное" ("холостое") прерывание
			; занимает только 9 команд.
;------------------------
			; * проверить, достигли ли мы одной секунды.
			; попадаем сюда только когда средний байт==0, 
			; то есть это может оказаться одна секунда.
			; попадаем сюда только 1 раз из каждых 256 раз.
			; (это наша самая оптимизированная проверка)
			; она достигается когда bres_mid==0.

tstf bres_hi		; также проверить старший байт на ноль
skpz			; z = и старший, и младший байты равны нулю - прошла секунда!
goto main_loop		; nz, нет, секунда ещё не прошла.

;-------------------------------------------------
; Попадаем сюда только если мы достигли одной секунды.
; теперь мы можем сгенерировать наше ежесекундное событие, например,
; подобное добавлению к нашим часам одной секунды, или какое-либо ещё.
; (в этом примере мы переключаем светодиод)
; Что нам ещё нужно сделать - так это прибавить 1,000,000
; к нашей 24-битной переменно и начать сначала.
;-------------------------------------------------
; Сначала прибавляем 1,000,000.
; Одна секунда = 1,000,000 = 0F 42 40 (в шестнадцатеричном формате)
; Поскольку мы знаем, что старший байт==0 и средний байт==0,
; это делает весь процесс очень быстрым.
; Это оптимизированное 24-битное сложение, поскольку мы можем
; просто загружать два старших байта и нам нужно выполнить настоящее
; сложение только с младшим байтом. Это намного быстрее, чем
; "должное" 24-битное сложение.

movlw 0x0F		; получаем значение старшего байта
movwf bres_hi		; загружаем его в старший байт нашей переменной

movlw 0x42		; получаем значение среднего байта
movwf bres_mid		; загружаем его в средний байт

movlw 0x40		; значение младшего байта, которое нужно прибавить
addwf bres_lo,f		; прибавляем его к остатку, содержащемуся в младшем байте
skpnc			; nc = переполнения нет, то есть значение среднего байта не меняется

incf bres_mid,f		; c, переполнение младшего байта - инкрементируем средний байт
			; это оптимизировано с расчётом на то, что значение среднего байта известно,
			; и средний байт не переполнится от одного инкремента.

			; Наше оптимизированное 24-битное сложение выполнено!
			; Это приблизительное в два раза быстрее "должного"
			; 24-битного сложения.
;-------------------------
			; теперь мы выполним "событие", которое мы выполняем каждую секунду.
			; Замечание! Для этого примера мы переключаем светодиод, который
			; таким образом даёт мигающий светодиод, который одну секундку включен
			; и одну секунду выключен.
			; Добавьте сюда ваш собственный код для вашего ежесекундного события.

			; Замечание! Мой светодиод подключен к porta,3
			; ваш светодиод может быть подключен к другому выводу.
movlw b'00001000'	; маска для бита 3
xorwf PORTA,f		; переключить PORTA,bit3 (переключить светодиод)
						
;-------------------------------------------------
			; теперь наше ежесекундное событие выполнено,
			; мы можем продолжить цикл.
goto main_loop
;------------------------------------------------------------------------------
;==============================================================================
end			; после этой точки нет никакого кода.
;==============================================================================

Исходный код программы с прерываниями


;******************************************************************************
; СЕКУНДНЫЙ ТАЙМЕР С НУЛЕВОЙ ПОГРЕШНОСТЬЮ
; ВЕРСИЯ С ПРЕРЫВАНИЯМИ
;
; для PIC 16F84 с частотой 4 МГц (или большинства PIC)
; (этот код был скомпилирован в MPLAB и аппаратно протестирован)
;
; Генерирует событие каждую секунду (или другой период) для любого PIC
; с любой тактовой частотой.
; Эта версия использует прерывания по переполнению таймера timer0.
; Код может быть адаптирован для различных тактовых частот, длин периодов
; и уровней точности.
;******************************************************************************

;==============================================================================
include <p16f84.inc>		; определение процессора
;==============================================================================
LIST b=5, n=97, t=ON, st=OFF	; настройки MPLAB
				; абсолютная табуляция кода=5, строк=97, обрезать длинные
				; строки=ON, таблица перекодировки символов=OFF

__CONFIG   _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC
				; настройки для 16F84; защита кода OFF, 
				; сторожевой таймер OFF, таймер при включении питания ON,
				; тип генератор XT - для кварца или керамического резонатора
				; частотой 4 МГц.
;==============================================================================
			; переменные
CBLOCK 0x20		; начало ОЗУ в 16F84

bres_hi			; старший байт нашей 24-битной переменной
bres_mid		; средний байт
bres_lo			; младший байт
			; (нам нужно только 3 байта для этой системы)
status_temp		; используется для обслуживания прерывания
w_temp			; используется для обслуживания прерывания

ENDC
;==============================================================================
			; код
org 0x000 		; установка начала памяти программ на вектор прерывания
reset
goto setup		; настроить прерывания и порты
org 0x004		; вектор прерывания, далее следует код обработчика прерывания.
;==============================================================================

;******************************************************************************
;  ОБРАБОТЧИК ПРЕРЫВАНИЯ     (этот код запускается при каждом прерывании таймера timer0)
;******************************************************************************
;------------------
int_handler
;------------------

;-------------------------------------------------
			; сначала мы сохраним регистры W и состояния
movwf w_temp      	; выгрузить текущее содержимое регистра W
movf	STATUS,w        ; поместить содержимое регистра состояния STATUS в регистр W
movwf status_temp       ; выгрузить содержимое регистра состояния STATUS

;-------------------------------------------------
; Замечание! мы попадаем сюда каждые 256 команд,
; теперь мы можем делать нашу специальную систему односекундного таймера.

; Jyf включает три основных шага:
; * вычесть 256 из нашей 24-битной переменной
; * проверить, достигнута ли контрольная точка
; * если да, прибавить 1,000,000 к 24-битной переменной и сгенерировать событие.
;-------------------------------------------------
			; * здесь используется оптимизированное 24-битное вычитание 
			; Оно сделано с минимумом команд.
			; Мы вычитаем 256 из 24-битной переменной,
			; просто декрементируя средний байт.

tstf bres_mid		; сперва проверим на mid==0
skpnz			; nz = нет потери значимости
decf bres_hi,f		; z, потеря значимости - тогда декрементируем самый старший байт (msb)

decfsz bres_mid,f	; декрементируем средний байт (вычитаем 256)

			; теперь полное 24-битное оптимизированное вычитание выполнено!
			; это примерно в 4 раза быстрее, чем "должное" 24-битное вычитание

goto int_exit		; nz, значит, секунда ещё не прошла.
			; в большинстве случаев полностью "фиктивное" ("холостое") прерывание
			; занимает только 9 команд.
;------------------------
			; * проверить, достигли ли мы одной секунды.
			; попадаем сюда только когда средний байт==0, 
			; то есть это может оказаться одна секунда.
			; попадаем сюда только 1 раз из каждых 256 раз.
			; (это наша самая оптимизированная проверка)
			; она достигается когда bres_mid==0.

tstf bres_hi		; также проверить старший байт на ноль
skpz			; z = и старший, и младший байты равны нулю - прошла секунда!
goto int_exit		; nz, нет, секунда ещё не прошла.

;-------------------------------------------------
; Попадаем сюда только если мы достигли одной секунды.
; теперь мы можем сгенерировать наше ежесекундное событие, например,
; подобное добавлению к нашим часам одной секунды, или какое-либо ещё.
; (в этом примере мы переключаем светодиод)
; Что нам ещё нужно сделать - так это прибавить 1,000,000
; к нашей 24-битной переменно и начать сначала.
;-------------------------------------------------
			; Сначала прибавляем 1,000,000.
			; Одна секунда = 1,000,000 = 0F 42 40 (в шестнадцатеричном формате)
			; Поскольку мы знаем, что старший байт==0 и средний байт==0,
			; это делает весь процесс очень быстрым.
			; Это оптимизированное 24-битное сложение, поскольку мы можем
			; просто загружать два старших байта и нам нужно выполнить настоящее
			; сложение только с младшим байтом. Это намного быстрее, чем
			; "должное" 24-битное сложение.

movlw 0x0F		; получаем значение старшего байта
movwf bres_hi		; загружаем его в старший байт нашей переменной

movlw 0x42		; получаем значение среднего байта
movwf bres_mid		; загружаем его в средний байт
movlw 0x40		; значение младшего байта, которое нужно прибавить
addwf bres_lo,f		; прибавляем его к остатку, содержащемуся в младшем байте
skpnc			; nc = переполнения нет, то есть значение среднего байта не меняется

incf bres_mid,f		; c, переполнение младшего байта - инкрементируем средний байт
			; это оптимизировано с расчётом на то, что значение среднего байта известно,
			; и средний байт не переполнится от одного инкремента.
			; Наше оптимизированное 24-битное сложение выполнено!
			; Это приблизительное в два раза быстрее "должного"
			; 24-битного сложения.
;-------------------------
			; теперь мы выполним "событие", которое мы выполняем каждую секунду.
			; Замечание! Для этого примера мы переключаем светодиод, который
			; таким образом даёт мигающий светодиод, который одну секундку включен
			; и одну секунду выключен.
			; Добавьте сюда ваш собственный код для вашего ежесекундного события.

			; Замечание! Мой светодиод подключен к porta,3
			; ваш светодиод может быть подключен к другому выводу.
movlw b'00001000'	; маска для бита 3
xorwf PORTA,f		; переключить PORTA,bit3 (переключить светодиод)
						
;-------------------------------------------------
; теперь наше ежесекундное событие выполнено, мы можем выйти из обработчика прерывания
;-------------------------------------------------
			; в заключение мы восстанавливаем регистры W и состояния, а также
			; сбрасываем флаг прерывания TMRO, которое мы только что обработали.
int_exit

BCF INTCON,T0IF		; сбросить флаг прерывания tmr0
movf status_temp,w     	; достаём копию регистра состояния STATUS
movwf STATUS            ; восстанавливаем содержимое регистра STATUS, которое было до прерывания
swapf w_temp,f
swapf w_temp,w         	; восстанавливаем содержимое регистра W, котрое было до прерывания

retfie			; возвращаемся из прерывания
;------------------------------------------------------------------------------

;******************************************************************************
;  НАСТРОЙКА     (выполняется только один раз при запуске)
;******************************************************************************
;------------------
setup			; переход на метку
;------------------

;-------------------------------------------------
; Замечание! версия для 16F84.
; Замечание! здесь мы настраиваем периферию и направления портов.
; Для разных PIC этот код нужно будет менять соответственным образом.
;-------------------------------------------------
			; настройка регистра OPTION
movlw b'10001000'	;
     ;  x-------	; 7, 0=включить, 1=отключить, подтяжка порта portb
     ;  -x------	; 6, 1=/, бит выбора фронта прерывания
     ;  --x-----	; 5, источник таймера timer0, 0=внутренний генератор, 1=внешний вывод.
     ;  ---x----	; 4, внешний фронт timer0, 1=\
     ;  ----x---	; 3, привязка предделителя, 1=wdt, 0=timer0
     ;  -----x--	; 2,1,0, значение предделителя таймера timer0
     ;  ------x-	;   000=2, 001=4, 010=8, 011=16, и т.д.
     ;  -------x	; 
			; Замечание! Мы привязываем предделитель к wdt, таким образом timer0
			; НЕ имеет предделителя и будет переполняться каждые 256 команд
			; и вызывать прерывание.

banksel OPTION_REG	; выбрать надлежащий банк регистров
movwf OPTION_REG	; загрузить данные в OPTION_REG
banksel 0		; вернуться к нормальному банку регистров bank 0
;-------------------------------------------------
			; установка направлений выводов порта PORTB
			; 1=вход, 0=выход
clrf PORTB		

movlw b'00000000'	; все 8 выводов порта portb устанавливаем как выходы

banksel TRISB		; выбрать надлежащий банк регистров
movwf TRISB		; установить маску на порт portb
banksel 0		; вернуться к нормальному банку регистров bank 0
;-------------------------------------------------
			; установка направлений выводов порта PORTA
			; 1=вход, 0=выход
clrf PORTA	
	
movlw b'00000000'	; все 5 порта porta устанавливаем как выходы,
			; (в 16F84 порт porta имеет только младшие 5 бит)
banksel TRISB		; выбрать надлежащий банк регистров
movwf TRISA		; установить маску на порт porta
banksel 0		; вернуться к нормальному банку регистров
;-------------------------------------------------
			; настройка регистра INTCON.
			; для данного примера кода мы включаем прерывание
			; по переполнению таймера timer0.
			; прерывания включаются последними (enable interrupts last)
			; установка прерывания
movlw b'10100000'	; GIE=on TOIE=on (прерывание по переполнению таймера timer0)
movwf INTCON		; установить.
;-------------------------------------------------
; Замечание! Теперь аппаратная часть настроена и нам нужно загрузить
; первый отсчёт (count) для одной секунды в нашу 24-битную переменную.
;-------------------------------------------------
			; Замечание! Этот пример использует тактовую частоту 4 МГц, которой
			; соответствует 1,000,000 отсчётов в секунду.
			; Нам нужен период в 1 секунду, поэтому мы должны загружать
			; 1,000,000 отсчётов каждый раз.
			; 1,000,000 = 0F 42 40 (в шестнадцатеричном формате)
			; Нам также нужно добавить 256 отсчётов для первого раза,
			; таким образом мы просто прибавляем 1 к среднему байту.
			; Если нужно, проверить переполнение среднего байта.
			; здесь мы загружаем 24-битную переменную.
movlw 0x0F		; получить значение старшего байта
movwf bres_hi		; загрузить в старший байт

movlw 0x42 +1		; получить значение среднего байта (заметьте, что мы прибавили к нему 1)
movwf bres_mid		; загрузить в средний байт

movlw 0x40		; получить значение младшего байта
movwf bres_lo		; загрузить в младший байт
			; теперь установка завершена, мы можем начать исполнение.
;-------------------------------------------------
goto main		; запустить главный цикл программы
;------------------------------------------------------------------------------

;******************************************************************************
;  MAIN     (главный цикл программы)
;******************************************************************************
;------------------
main			; переход на метку
;------------------
;-------------------------------------------------
; Замечание! Этот пример использует прерывание по переполнению таймера timer0.
; Оно будет прерывать наш главный программный цикл каждые 256 команд
; и выполнять систему односекундного таймера.
;-------------------------------------------------
main_loop		; 
			; Замечание! здесь располагается ваш основной программный код,
			; или вызовы к частям основной программы.
			; Прерывание выполняет весь код односекундного таймера.
; код
; код
; код
; код
;-------------------------------------------------
goto main_loop		; переход на начало главного цикла.
;------------------------------------------------------------------------------
;==============================================================================
	end		; после этой точки нет никакого кода.
;==============================================================================



© PIClist-RUS (piclist.ru), 2007 г.

PIClist RUS (piclist.ru) © 2008
все права сохранены. перепечатка статей и переводов с данного сайта запрещена.