Главная Мой профиль Регистрация Выход Вход
Приветствую Вас Гость | RSS
Воскресенье
19.05.2024
09:12
Articles
Меню сайта
Категории каталога
Счетчик TMR0 [2]
Прерывания [2]
ШИМ на pic16f877 [1]
Configuration Bits in Mid-Range PIC Microcontrollers [1]
Индикация [1]
Мини-чат
Главная » Статьи » Программирование MK PIC » Счетчик TMR0

Работа со счетчиком TMR0 , константы в формулах

> Разработка электронных устройств на PIC – контроллерах.

 

Перед Вами выпуск, посвященной разработке устройств на PIC-контроллерах. В ней я думаю рассмотреть основные этапы создания этих устройств можно сказать «с нуля». Начальные выпуски рассылки будут рассчитаны на тех, кто с PIC-контроллерами дела еще не имел, а темы для дальнейших рассылок я думаю выбирать исходя из полученных откликов, так что пишите. Планирую выдавать свежий номер рассылки еженедельно на выходные, а как получится, посмотрим.
Итак, прежде всего я хотел бы указать на справочные материалы, которые желательно если не изучить, то хотя бы иметь под рукой. Это:
-Тех. описания контроллеров на русском языке:
1.Однокристальные микроконтроллеры Microchip: PIC16C8x, Рига, ORMIX, 1997г;
2.В.А. Ульрих. Микроконтроллеры PIC16C7x, Наука и техника, 2000г;
3.Однокристальные микроконтроллеры PIC17C4x, PIC17C75x, M3820, Додека, 1998г;
4.Однокристальные микроконтроллеры PIC12С5х, PIC12C6x, PIC16x8x, PIC14000, M16C/61/62, Додека, 2000г;
-CD с сайтом фирмы-производителя. На нем находится вся документация по всей продукции. Правда на английском. Адрес сайта www.microchip.com

Для начала достаточно иметь документацию на PIC16F84A, который наиболее удобен для начала изучения всего семейства PIC-micro. Освоив этот контроллер легко можно перейти на любой другой. Разработка устройств на микроконтроллерах обычно производится в несколько следующих этапов. Первый – это постановка задачи и составление алгоритма работы устройства. Как это делать, было хорошо описано в журнале «Радио» N11,12 за 2000г. Поэтому пока сильно заострять внимание на этом этапе не буду. Отмечу лишь, что это один из важнейших этапов, который определяет всю дальнейшую работу.
После того, как появился какой-то алгоритм, представляющийся работоспособным, разработчик садится за компьютер и начинает писать программу (обычно не всю сразу, а по частям). Параллельно с написанием программы собирается макет устройства, на котором будет производиться отладка. Как только текст программы готов, производится ассемблирование, т.е. перевод мнемонических ассемблерных кодов в двоичный формат, который затем можно «зашить» в ПЗУ контроллера с помощью специального программатора. Однако, обычно после первого ассемблирования в контроллер программа не «шьется», т.к. даже у самого квалифицированного программиста в ней наверняка будут логические ошибки. Поэтому прежде исполнение программы проверяется в специальном программном эмуляторе контроллера, в котором и устраняются найденные ошибки. Далее производится прошивка контроллера и отладка программы уже на работающем макете устройства.
В принципе текст программы можно писать в обычном текстовом редакторе, сохраняя его в файле с расширением .asm. Далее этот файл можно ассемблировать, загружать в программный эмулятор или «прошивать» ПЗУ контроллера. Лет пять назад примерно так и делалось. Однако сейчас существует так называемая интегрированная среда разработки (IDE – integrated development environment), в которой сосредоточены все инструментальные средства - MPLAB. Разработана она производителем PIC-контроллеров и распространяется бесплатно. Загрузить ее можно с сервера Microchip. На данный момент вышла уже версия 5.20.
Что же представляет собой PIC-контроллер с точки зрения программиста? Прежде всего это набор ячеек памяти, называемых регистрами. Все имеющиеся в распоряжении программиста команды в конечном счете предназначены для изменения содержимого регистров. Каждый регистр имеет свой уникальный адрес, зная который можно к нему обратиться и произвести с ним требуемую операцию. Все вместе адреса образуют адресное пространство контроллера, или иначе – ОЗУ. Физически регистры объединены между собой через шину данных. Так же к шине данных подключены АЛУ и рабочий регистр W, который собственного адреса не имеет, и через который производится большинство операций контроллера. Все регистры являются восьмибитными, хотя в некоторых из них реально доступно меньшее число бит. Шина данных так же восьмибитная. Поэтому контроллер и называется восьмибитным.
Регистры делятся на две категории: регистры общего назначения (РОН) и регистры специального назначения. Регистры общего назначения – это просто ячейки памяти, предназначенные для хранения байта информации. Регистры специального назначения определяют режимы работы самого контроллера. Например, порты ввода/вывода являются регистрами специального назначения. Для того, что бы, допустим, считать значение входного логического сигнала на выводе N8 контроллера PIC16F84A, необходимо прочитать значение бита 2 регистра специального назначения с адресом 0x06.
Помимо ОЗУ, на кристалле контроллера обязательно имеется память программ (ПЗУ). Именно в нее и записывается исполняющийся код, который получается после ассемблирования текста программы. У каждой ячейки, находящейся в ПЗУ так же имеется свой уникальный адрес. Непосредственного доступа к памяти программы у программиста нет. Он только может менять адрес той команды, которая должна быть исполнена. Этот адрес доступен через два регистра специального назначения, которые называются программным счетчиком. В этом счетчике находится адрес той команды, которая должна быть исполнена. По включению контроллера или после сброса, в счетчике будет записан 0 (для PIC16F84A). Таким образом, первой будет исполнена команда, находящаяся в ПЗУ по адресу 0. После исполнения команды, содержимое счетчика автоматически увеличивается на единицу, и далее будет выполнена команда, расположенная в ПЗУ по адресу 1. Так будет продолжаться до тех пор, пока не исполнится команда перехода, которая принудительно запишет в программный счетчик какое-нибудь другое значение.
Рассмотрим теперь более детально основные функции регистров специального назначения. Каждый регистр имеет свое собственное название. Поэтому при рассмотрении этих регистров будут указаны их названия, а не абсолютные адреса в пространстве ОЗУ.
Два регистра я уже упоминал. Это - программный счетчик, который состоит из двух регистров: PCL и PCLATH. В регистре PCL находится младший байт счетчика, в PCLATH – старшие 5 бит. Таким образом, программный счетчик может адресовать 13 бит адреса ПЗУ(8192 команды). В большинстве контроллеров реально существует меньше памяти программы. Например, в PIC16F84A всего 1024 команды, в PIC16C65A – 4096, а в PIC16F877A – все 8192. При работе с данными регистрами существует ряд правил, которые будут изложены далее на примерах. А сейчас перейдем к другим регистрам.
Регистры TRIS (TRISA, TRISB… TRISE). Эти регистры определяют направление линий ввода/вывода портов процессора. Число их равно числу портов. Например, если в TRISB бит 5 установлен в 0, то соответственно линия 5 PORTB будет выходом (для PIC16F84A это вывод N11). И наоборот, если этот бит установлен в 1, то соответствующая линия будет входом. Из этого следует, что в начале работы программы необходимо инициализировать эти регистры, т.е. записать в них те значения, которые бы задали назначение линий ввода/вывода процессора.
Регистр INTCON. Этот регистр управляет прерываниями. Если прерывания не нужны, то бит 7 (он называется GIE) должен быть установлен в 0. Этот бит разрешает или запрещает все прерывания сразу. Если он установлен, то какое–то конкретное прерывание можно разрешить/запретить другим битом. Например, прерывание от таймера задается битом 5 (TOIE). Этот регистр должен быть инициализирован в начале программы. Более подробно работу прерываний я рассмотрю ниже на примерах.
Регистр STATUS. В этом регистре находятся биты, значение которых зависит от результата каких-то действий процессора. Например, если была выполнена арифметическая или логическая операция, и резуль-тат оказался равным нулю, то будет установлен бит Z, в противном случае этот бит будет сброшен. Программа может проверить этот бит, и в зависимости от результата, предпринять какие-то действия. Аналогично – биты переполнения С и DC. Проверив биты TO и PD, можно определить, по какой причине про-изошел сброс процессора – по включению питания или по срабатыванию таймера. Помимо вышеуказанных, в регистре STATUS находятся биты переключения банков ОЗУ и страниц памяти программы. Что это такое – рассмотрим ниже.
Регистр OPTION предназначен для управления таймером и сторожевым таймером. Так же он позволяет подключать ко входам PORTB подтягивающие резисторы и задавать фронт импульса, по которому будет происходить прерывание со входа RB0. Этот регистр так же должен быть инициализирован при запуске программы.
В PIC-контроллерах ОЗУ разбито на несколько банков данных. Для прямого обращения к регистру какого-то банка необходимо сначала задавать сам банк битами RP0 и RP1 регистра STATUS. Регистры специального назначения занимают младшие адреса каждого банка, а затем за ними идут регистры общего назначения. Например, в PIC16F84A имеются два банка (нулевой и первый), однако в первом банке расположены всего пять регистров специального назначения: OPTION, TRISA, TRISB, EECON1 и EECON2. Если попытаться обратиться по какому-нибудь другому адресу в первом банке, то это будет равносильно обращению к нулевому банку. Для составления более четкого представления об ОЗУ PIC-контроллера рекомендую внимательно изучить карту памяти какого-нибудь процессора, лучше всего для PIC16F84A.
Теперь о стеке. У PIC-контроллеров имеется только аппаратный стек, прямой доступ к нему отсутствует (таких команд, как POP и PUSH нет). Он предназначен только для сохранения адресов возврата при вызове подпрограмм или при обработке прерывания. При вызове подпрограммы текущее значение адреса помещается в первую ячейку стека, а все остальное сдвигается. При выполнении команды возврата аналогично адрес берется из первой ячейки. Количество ячеек стека (уровень стека) в разных PIC-контроллерах разное: от 2 до 16. Поэтому при разработке программы необходимо следить, что бы вложенность подпро-грамм с учетом возможных прерываний не превысило уровень стека. В противном случае возврат из подпрограммы или прерывания будет произведен по случайному адресу.
В заключении о сторожевом таймере (WDT). Его основное назначение – предотвращать возможное зависание программы. Представляет собой он обычный встроенный RC-генератор с делителем. Среди команд контроллера имеется команда сброса сторожевого таймера (clrwdt). При выполнении этой команды обнуляется делитель RC-генератора. Если делитель не обнулить, то он переполнится и произведет сброс контроллера, после чего выполнение программы начнется заново. Поэтому программа, использующая WDT, должна успевать его обнулить. В случае зависания WDT не будет обнулен и произойдет сброс процессора, т.е. программа выйдет из состояния зависания. Эффективность использования таймера зависит от построения программы. Желательно команду clrwdt использовать не более одного раза. Типичное время работы сторожевого таймера равно примерно 18мс. Его можно увеличить, подключив к нему делитель с заданным в регистре OPTION коэффициентом деления (от 1 до 128).
Итак, приступим к созданию простейшей программы. Одновременно начнем разбирать основные возможности интегрированной среды разработки MPLAB, одной из последних ее версий – V5.11. Напомню, что самую последнюю версию можно загрузить с сервера Microchip –www.microchip.com (если, конечно, у вас хороший доступ к интернет, т.к. загружать придется более 10 мБ). Работает MPLAB во всех средах WINDOWS, начиная с 3.11.
Для того, что бы написать программу, прежде всего необходимо создать проект. Проект представляет собой обычный текстовый файл с расширением .pjt, в котором хранятся все настройки, имеющие отношение к какой-то разрабатываемой программе. При желании этот файл можно редактировать или даже создавать в обычном текстовом редакторе, однако в этом нет необходимости, т.к. за Вас все сделает MPLAB. Создается проект командой «Project -> New Project». По выбору этой команды появится диалоговое окно сохранения файла, в котором необходимо задать имя проекта. Давайте зададим имя test.pjt. После нажатия на клавишу OK, будет выведено окно с названием «Edit Project», в котором задаются все настройки проекта. В строке «Target Filename» Вы увидите только что заданное имя проекта с расширением .нех .Под этим именем после ассемблирования программы будет создан файл с кодами для прошивки процессора. В строке «Include Path» указываются пути к файлам проекта, находящимся не в директории проекта. Эти пути должны быть написаны через точку с запятой. В нашем случае все файлы будут храниться в одном каталоге, поэтому этом поле можно оставить пустым. Аналогично в строке «Library Path» указывается путь к файлам библиотек, а в «Linker Script Path» - путь к файлам скриптов линковщика. Эти поля тоже оставим пустыми.
Далее нажмите на кнопку «Change…», после чего появится окно выбора режимов разработки проекта «Development Mode». Наиболее простой режим – это режим редактора. В нем можно просто написать программу и проассемблировать ее. Этим режимом мы пользоваться не будем, т.к. помимо текстового написания программы, ее нужно еще и отладить. Для отладки в MPLAB имеется несколько режимов работы с эмуляторами. Нас будет интересовать «MPLAB-SIM Simulator». Это режим работы со встроенным программным эмулятором, имеющим достаточно большие возможности. После его выбора станут активными другие вкладки окна «Development Mode». Перейдите к выпадающему списку типов процессора и выберите требуемый (в данном случае PIC16F84A). Чуть ниже в текстовом окне будет написано, что не вся переферия поддерживается эмулятором. Для того, что бы узнать, что не поддерживается для данного процессора более конкретно, нажмите кнопку «Details».
Затем перейдите на вкладку «Clock». Здесь можно выбрать тип генератора и тактовую частоту. Зададим ее равной частоте часового кварца 32768 Гц. Далее перейдем к вкладке «Configuration». В ней задается режим работы сторожевого таймера. «None» - таймер выключен. «WDT Chip Reset Enable» - по срабатыванию таймера произойдет сброс процессора. «WDT Break Enable» - По срабатыванию таймера исполнение программы будет остановлено. Выберем «None». В заключение щелкните по вкладке «Break Options». В ней находятся установки, имеющие отношения к работе с точками останова программы. Оставьте здесь все по умолчанию. Нажмите на «OK» окна «Development Mode». Возможно после этого Вы получите ряд предупреждений, однако для начала на них можно не обращать внимания. Замечу, что окно «Development Mode» доступно и через меню «Options».
В окне «Language Tool Suite» выбирается набор инструментальных средств разных компаний. Нас интересует «Microchip» (по умолчанию).
Если выделить мышью в поле «Project Files» название «test [.hex]», и нажать кнопку «Node Properties» то появится окно задания установок ассемблера. Пока в нем ничего менять не надо, позже рассмотрим на-значение этих настроек.
Теперь нажмите на «OK» окна «Edit Project» и файл проекта будет создан. Сохраните его («File -> Save»). Посмотреть установки проекта можно командой «Window -> Project».
Далее создадим файл, в котором будет находится текст нашей программы. Это выполняется командой «File -> New». Сохраним его под названием «test.asm» в том же каталоге, где был сохранен файл проекта. Теперь необходимо подключить его к проекту. Для этого опять вызовем окно «Edit Project» командой «Project -> Edit Project». В нем нажмем кнопку «Add Node». При этом появится диалоговое окно загрузки файла, в котором необходимо выбрать только созданный файл программы test.asm. После выбора «test [.asm]» появится в поле «Project Files» под строкой «test [.hex]». Это будет означать, что данный файл подключен к проекту. Сохраните проект еще раз.
Это, собственно говоря, и все основные моменты создания проекта. Написано много, но пусть Вас это не пугает – на практике на создание проекта тратится не более полуминуты.

После того, как мы создали проект, можно приступать собственно к написанию программы. Пусть наша первая программа выполняет какую-нибудь простейшую задачу, например, мигает светодиодом с частотой 1Гц. Алгоритм ее работы получится следующий. По включению контроллера программа выставит единицу на линию, к которой подключен светодиод, подождет 0,5 сек, выставит нуль, опять подождет 0,5 сек, а затем повторит все заново. Напомню, что мы будем писать программу для PIC16F84A, используя генератор с частотой 32768Гц. Светодиод подключим к линии RB0 (через резистор), зажигать его будем единицей.
Как уже упоминалось выше, каждый регистр специального назначения контроллера имеет свое название. В ассемблере для обращения к какому-то регистру можно использовать либо адрес этого регистра, либо его название. Например, записать значение из регистра W в регистр INTCON можно как командой movwf 0x0B так и командой movwf INTCON. Использование названия более предпочтительно, т.к. оно информативнее для программиста. Однако для того, что бы использовать название регистра сначала необходимо сопоставить само название числовому значению адреса. Сопоставление производится директивой ассемблера EQU. Например, INTCON EQU 0x0B. После этого при ассемблировании текста программы ассемблер при встрече строки "INTCON” будет вместо нее вставлять ее числовое значение "0х0B”. Таким образом, все символьные имена регистров, используемых в программе, должны быть предварительно описаны. Однако самостоятельно описывать регистры специального назначения необходимости нет, т.к. в MPLAB уже включены файлы с описаниями этих регистров для каждого PIC. Эти файлы необходимо только подключить к основной программе командой «#include». В нашем случае в начале программы нужно написать строку «#include p16f84a.inc».
Кроме регистров специального назначения в программе будут использоваться и регистры общего назначения. Их надо описывать самостоятельно. Это можно делать так же с помощью директивы EQU, присвоив каждому символьному названию свой адрес. Однако это несколько громоздко. Проще воспользоваться директивами «cblock» и «endc». Эти директивы позволяют задать один начальный адрес, а ассемблер сам присвоит числовые значения заданным именам по возрастанию. В нашей программе мы будем пользоваться всего двумя регистрами – счетчиками времени при формировании задержки на 0,5 сек. Назовем их CounterLo и CounterHi. Таким образом, в программе необходимо написать следующий текст:
CODE
cblock 0x0C
CounterLo
CounterHi
endc

0x0C – это адрес нулевой ячейки регистров общего назначения. После ассемблирования строке CounterLo будет присвоен адрес 0x0C, а строке CounterHi адрес 0x0D.
Теперь необходимо задать константы. Они задаются так же директивой EQU:
CODE
INIT_PORTA EQU b'00000000'
INIT_PORTB EQU b'00000000'
INIT_OPTION EQU b'00000000'
INIT_INTCON EQU b'00000000'
DELAYLO EQU .79
DELAYHI EQU .21

Первые четыре константы будут использоваться при инициализации соответствующих регистров. Т.к. они редназначены для инициализации отдельных битов, то их удобно задавать в двоичном формате в виде b’x… x’. Следующие две – в подпрограмме задержки на 0,5сек. Они заданы в десятичном формате, который обозначается точкой. Если вместо «.21» написать «21», то ассемблер воспримет это как шестнадцатеричный формат, который установлен по умолчанию.
Напишем подпрограмму инициализации:
CODE
Init
 bsf STATUS,RP0
 movlw INIT_PORTA
 movwf TRISA
 movlw INIT_PORTB
 movwf TRISB
 movlw INIT_OPTION
 movwf OPTION_REG
 bcf STATUS,RP0
 movlw INIT_INTCON
 movwf INTCON
 clrf STATUS
 return

При входе в подпрограмму производится выбор первого банка данных ОЗУ установкой бита RP0. Далее производится выбор направления линий ввода/вывода. Т.к. у контроллера задействована всего одна линия на выход, то во все регистры TRIS можно записать нули, т.е. линии будут выходами. В регистры OPTION и INTCON также можно записать нули, т.к. таймер, сторожевой таймер и прерывания сейчас использоваться не будут. После этого производится обнуление всех битов регистра STATUS, что вызывает переключение на нулевой банк ОЗУ.
Теперь составим подпрограмму задержки на 0,5 сек:
CODE
Delay05
 movlw DELAYLO
 movwf CounterLo
 movlw DELAYHI
 movwf CounterHi
Del05
 decfsz CounterLo,1
 goto Del05
 decfsz CounterHi,1
 goto Del05
 return

и основное тело программы:
CODE
Begin
 call Init
 bsf PORTB,0
 call Delay05
 bcf PORTB,0
 call Delay05
 goto Begin
 end

Нажмите F10 для ассемблирования. По окончании ассемблирования Вы получите три сообщения «Register in operand not in bank 0. Ensure that bank bits are correct.». Этими сообщениями ассемблер напоминает программисту, что он производит запись в регистры, находящиеся не в нулевом банке. Эти сообщения можно отключить, если, например, вместо «movwf TRISA» написать «movwf TRISA^0x80».

Рассмотрим теперь подробно работу написанной ранее программы. Ниже я еще раз привожу полный ее текст с комментариями.
CODE
#include p16f84a.inc;Подключение файла с описаниями регистров специального назначения
;Определение используемых в программе регистров общего назначения:
 cblock 0x0C  
  CounterLo;младший байт счетчика в подпрограмме (далее – пп.) задержки
  CounterHi;старший
 endc
;Константы инициализации регистров специального назначения:
INIT_PORTA EQU b'00000000';Все линии на выход
INIT_PORTB EQU b'00000000';
INIT_OPTION EQU b'00000000';Константа для инициализации регистра OPTION.
   ;Т.к. таймер, сторожевой таймер и подтягивающие
   ;резисторы не используются, то в него можно
   ;просто записать 0
INIT_INTCON EQU b'00000000';Прерывания запрещены
;Константы для пп. задержки на 0,5 сек:
DELAYLO EQU .79;
DELAYHI EQU .6;
;Далее начинается исполняемый код программы:
 goto Begin  ;Переход на начало
int_point org 0x04;Вектор прерывания
;Пп. задержки на 0,5сек
Delay05
;Инициализация регистров счетчика:
 movlw DELAYLO;Младший байт
 movwf CounterLo;
 movlw DELAYHI;Старший байт
 movwf CounterHi;
Del05
 decfsz CounterLo,1;Декремент младшего байта счетчика. Выполняется до тех пор,
 goto Del05;пока он не станет равным нулю, после чего декрементируется
 decfsz CounterHi,1;старший байт
 goto Del05;
 return  ;После того, как оба байта станут равными нулю, возврат из пп.
;Инициализация
Init
 bsf STATUS,RP0;Выбор первого банка ОЗУ
movlw INIT_PORTA;Инициализация регистров выбора направления линий  movwf TRISA ^80h;ввода/вывода
 movlw INIT_PORTB;
 movwf TRISB ^80h;
 movlw INIT_OPTION;Инициализация регистра OPTION
 movwf OPTION_REG ^80h
 clrf STATUS;Переключение на нулевой банк ОЗУ
 movlw INIT_INTCON;Инициализация регистра INTCON
 movwf INTCON;
 return  ;Возврат
Begin
 call Init  ;Инициализация
 bsf PORTB,0;Включить светодиод
 call Delay05;Подождать 0,5сек
 bcf PORTB,0;Выключить светодиод
 call Delay05;Подождать 0,5сек
 goto Begin;Переход на начало
 end  ;Конец программы

Еще раз на всякий случай проассемблируйте программу (F10). В окне «Build results» Вы не должны получить никаких сообщений. Откройте окно «Program Memory Window» (Window -> Program Memory). В нем Вы сможете увидеть расположение программы в ПЗУ контроллера. Напомню, что выполнение программы в PIC16F84 начинается с нулевого адреса. Поэтому, как видно в «Program Memory Window», первой после сброса будет исполнена команда «goto Begin».
Для того, что бы увидеть, как это происходит в эмуляторе, произведите его сброс клавишей F6. После этого строка «goto Begin» выделится темной линией. Это произойдет как в исходном тексте программы, так и в окне «Program Memory Window». Нажмите F7 для выполнения данной команды, после чего программа совершит переход по адресу ПЗУ, обозначенному меткой Begin, обойдя подпрограммы Delay05 и Init. Вместо метки можно было бы указать адрес напрямую (goto 0x018), однако при написании программы он обычно заранее не известен. Поэтому удобнее пользоваться метками, а ассемблер при ассемблировании сам подставит вместо меток соответствующие адреса. Единственное требование к меткам – они должны располагаться в первом столбце, иначе ассемблер выдаст предупреждение «Found label after column 1».
В рассматриваемом контроллере переход командой «goto» можно совершить напрямую по всему пространству памяти программ. Однако существует ряд контроллеров, в которых ПЗУ разбито на страницы, которые иногда перед использованием «goto» необходимо переключать. Сейчас на этом моменте мы останавливаться не будем, страничную организацию ПЗУ рассмотрим в следующих выпусках.
Следующая исполняемая команда будет «call Init». По сути она аналогична «goto», т.е. она совершит переход на метку «Init» (или по адресу 0x00E, что одно и то же). Единственное ее отличие состоит в том, что при ее выполнении, в аппаратный стек контроллера будет занесен адрес следующей за ней команды, а значения, находящиеся в стеке, сдвинуты. В данном случае адрес «call Init» - 0х018. Поэтому в стек будет занесено 0x019.
Откройте окно «Stack Window» командой «Window ->Stack». В нем будет строка «Return Address:», и больше ничего. Это означает, что в стек пока записи не производилось. Нажмите F7. Программа перейдет по адресу, обозначенному меткой «Init», а в окне «Stack Window» появится «0019 (call Init)». Таким образом начнется исполнение подпрограммы инициализации.
Для того, чтобы увидеть работу подпрограммы инициализации, откроем окно просмотра содержимого регистров ОЗУ (Window ->Watch Windows -> New Watch Window). По открытию пользователю сразу предлагается ввести название какого-нибудь регистра. Давайте введем регистр «STATUS» и нажмем «Enter», а затем «Esc». В окне «Watch-1» будет находиться регистр «STATUS» с указанием его текущего значения в шестнадцатеричном формате. Т.к. для этого регистра шестнадцатеричная форма представления не очень удобна, изменим ее на двоичную. Для этого командой «Window ->Watch Windows -> Edit Active Watch» вызовите окно «Edit Watch Symbols», выберите в нем регистр «STATUS» и нажмите кнопку «Properties». Далее в появившемся окне «Properties» в рамке формат выберите «Binary» и нажмите «ОК», а затем «Close». Помимо шестнадцатеричного и двоичного представлений, можно так же просматривать значения регистров в десятичном (Decimal) и текстовом (ASCII) форматах. Добавление нового регистра в окно «Watch-1» производится нажатием клавиши «Insert», а удаление «Delete». Добавьте так же регистр «w» (можно во всех форматах), а так же регистры «trisa» «trisb» «option_reg», «INTCON» «PORTB» в двоичных форматах.
Нажмите F7. При этом будет исполнена команда «bsf STATUS,RP0», т.е. установится пятый бит регистра «STATUS», что вызовет переключение на первый банк ОЗУ. (По сбросу контроллера включен нулевой банк.) Измененный регистр в окне «Watch-1» выделяется красным цветом.
Далее производится запись инициализирующих значений в регистры «TRISA» «TRISB» «OPTION_REG» «INTCON». Как уже упоминалось ранее, все операции пересылки производятся через регистр «W». Поэтому сначала константа записывается в «W» командой «movlw _имя_константы». Эта команда так и называется – загрузка константы в регистр «W» (Move Literal to W). Вместо имени константы можно указать ее значение напрямую. Нажмите F7. Т.к. мы загружаем нуль, и в регистре «W» перед выполнением операции так же находился нуль, то содержимое регистра W не меняется и он выделяться красным в окне «Watch-1» не будет.
Далее выполним команду «movwf TRISA», т.е. перешлем нуль из «W» в «TRISA» и определим линии PORTA как выхода.
Аналогично запишем константы в регистры «TRISB» и «OPTION_REG».
Затем командой «clrf STATUS» выберем нулевой банк. Этой командой производится обнуление заданного регистра. Обратите внимание, что при ее выполнении устанавливается бит равенства нулю «Z» регистра «STATUS», так что полностью сбросить в 0 «STATUS» с помощью «clrf» невозможно. После «clrf STATUS» производится инициализация регистра «INTCON» и выполняется команда возврата из подпрограммы «return». При этом из стека будет извлечен адрес возврата и записан в программный счетчик (смотрите окно «Stack Window»). Т.е. следующей исполняемой командой будет команда с адресом в ПЗУ 0х19 (bsf PORTB,0 – установка бита 0 в регистре PORTB). Выполним ее. При этом на линию, к которой подключен светодиод будет подана единица и светодиод загорится.
Разберем, как работает подпрограмма задержки Delay05. Нажмите F7 и войдите в нее. Сначала производится инициализация регистров «CounterLo» и «CounterHi». Добавьте их в окно «Watch-1» и выполните первые четыре команды подпрограммы Delay05. Вы увидите, как в них записались заданные в константах «DELAYLO» и «DELAYHI» значения. Затем начинает выполняться цикл декремента значений регистров до тех пор, пока они не станут равными нулю. Это происходит ровно за 0,5 сек. Декремент производится командой «decfsz имя_регистра, назначение». Она может выполняться как за два, так и за один машинный цикл. (Машинный цикл равен четырем периодам тактового генератора) Если заданный регистр после декремента не равен нулю, то команда занимает один цикл и затем выполняется следующая за ней команда. А если регистр становится равным нулю, то следующая команда пропускается и ее выполнение занимает два цикла. После имени регистра через запятую указывается регистр, в который помещается результат (0 или 1). Если указан «0», то результат помещается в регистр «W», а если «1», то в декрементируемый регистр. В нашем случае команда «decfsz CounterLo,1» декрементирует регистр «CounterLo» и помещает результат обратно в этот же регистр. Если после декремента «CounterLo» не равен нулю, то выполняется команда «goto Del05», занимающая два машинных цикла. Таким образом, на один декремент «CounterLo» приходится три машинных цикла. Когда «CounterLo» станет равным нулю, будет произведен декремент регистра «CounterHi» и весь цикл повторится, пока и «CounterHi» не станет равным нулю.
Если продолжить выполнение программы, нажимая F7, то для выхода из подпрограммы Delay05 придется нажать достаточно большое число раз, что, разумеется, неудобно. Однако в эмуляторе имеется возможность задания точек останова, т.е. задания таких команд из текста программы, перед выполнением которых, эмулятор остановится. Щелкните правой кнопкой мыши по строке с командой «return». В появившемся меню выберите «Break Point(s)». После этого данная строка выделится красным цветом. Далее запустите программу на исполнение, нажав клавишу F9. Остановка произойдет на команде «return». Нажмите F7 и выйдите из подпрограммы Delay05.
Далее выполните следующую команду «bcf PORTB,0» (сброс бита 0 в регистре «PORTB»), что вызовет выключение светодиода.
Если опять нажать F7, то произойдет повторный вход в подпрограмму Delay05, в чем смысла нет. Давайте теперь проверим, действительно ли данная подпрограмма выполняется в течении 0,5 сек (или 4096 машинных цикла). Сначала необходимо убрать точку останова. Для этого опять щелкните по ней правой кнопкой мыши и выберите в меню «Break Point(s)». Потом откройте окно «Stopwatch» командой «Window -> Stopwatch…». Убедитесь, что тактовая частота контроллера установлена равной 32768Гц. В противном случае вызовите окно «Development Mode» (Options -> Development Mode) и задайте ее там. Затем нажмите кнопку «Zero». В окошках «Cycles» и «Time» появятся нули. Далее щелкните по тексту программы и запустите подпрограмму Delay05 на исполнение, нажав F8. При этом в окне «Stopwatch» можно будет наблюдать время и число машинных циклов. Как только подпрограмма будет выполнена, эмулятор остановится, а в окне «Stopwatch» можно будет увидеть время 500.00 ms и число циклов - 4096. Нажмите еще раз F8, и программа перейдет на начало. Таким образом, нажатие F8 позволяет (так же как и F7) исполнять программы пошагово, однако при этом вход в подпрограммы не производится.
Давайте вкратце обобщим команды и директивы ассемблера, использованные в программе test.asm или аналогичные им.
Команда / Описание

goto/Переход по адресу ПЗУ. Обычно адрес задается меткой в программе. Выполняется за 2 цикла. Пример:
goto Begin
decfsz/регистр, назначение Декремент заданного регистра и проверка результата на равенство нулю. Если результат равен нулю, то следующая команда пропускается, и выполнение занимает два цикла. В противном случае ее выполнение занимает один цикл. Данную команду удобно применять при организации различных счетчиков, задержек и т.д.
Пример: decfsz CounterLo,1.
incfsz / регистр, назначение Данная команда аналогична предыдущей, только вместо декремента производится инкремент.
decf / регистр, назначение Декремент без проверки на равенство результата нулю. Выполняется за один цикл.
incf / регистр, назначение Инкремент без проверки на равенство результата нулю. Выполняется за один цикл.
call / Вызов подпрограммы. При вызове производится помещение адреса следующей команды в аппаратный стек со сдвигом всего его содержимого. Выполняется за два цикла.
Пример: call Delay05.
return / Возврат из подпрограммы. Этой командой производится переход по адресу возврата, находящемуся в вершине стека. Выполняется за два цикла.
retlw / константа То же самое, что и предыдущая команда, только при возврате выполняется загрузка заданной константы в регистр W. Применяется при организации таблиц в ПЗУ. Будет рассмотрена в будущих рассылках.
Пример: retlw 0x23.
clrf / регистр Запись нулевого значения в заданный регистр. Выполняется за один цикл.
movlw / константа Загрузка заданного числа в регистр W. Выполняется за один цикл.
Пример: movlw INIT_PORTA.
movf / регистр, назначение Если назначение = 0, то производится пересылка значения из заданного регистра в регистр W. При назначение = 1 пересылка производится из заданного регистра в тот же самый регистр. Смысл этой операции состоит в том, что при пересылке производится сброс или установка бита равенства нулю Z регистра STATUS, и поэтому данную команду можно использовать для проверки содержимого регистра на равенство нулю, не меняя значение в регистре W.
Выполняется за один цикл.
movwf / регистр Пересылка значения из W в указанный регистр. Выполняется за один цикл.
Пример: movwf TRISA.
bsf / регистр, номер бита Установка бита с заданным номером в заданном регистре. Выполняется за один цикл.
Пример: bsf STATUS,RP0.
bcf / регистр, номер бита Сброс бита с заданным номером в заданном регистре. Выполняется за один цикл.
Пример: bcf STATUS,RP0.

Директива ассемблера / Описание
include / Подключение к программе заданного файла. Ее выполнение аналогично тому, если бы текст включаемого файла содержался бы в тексте самой программы.
Пример: #include p16f84a.inc
equ / Присваивание числового значения текстовой строке. Пример: INIT_PORTA EQU b'00000000'
cblock, endc / Задает список констант, присваивая каждой число на единицу больше предыдущей.
Пример:
CODE
cblock 0x0C
                     CounterLo
                     CounterHi
               endc

Вернемся еще раз к подпрограмме задержки Delay05. В прошлой рассылке в эмуляторе было проверено, что она действительно выполняется за 4096 циклов или при тактовой частоте 32768 Гц за 0,5 сек. Составим теперь формулу, представляющую собой зависимость числа циклов выполнения подпрограммы от чисел, задаваемых в регистрах CounterLo и CounterHi.
Сначала подсчитаем, за сколько машинных циклов производится декремент регистра CounterLo. Если результат выполнения команды «decfsz CounterLo,1» не равен 0, то эта команда занимает один цикл, а за ней следует «goto Del05», которая выполняется за два цикла. Если результат равен нулю, то команда «decfsz CounterLo,1» выполняется за два цикла. Таким образом, получается, что все время декремент CounterLo выполняется за три цикла, за исключением последнего раза, когда это происходит за два цикла. Т.е. декремент производится за 3*DELAYLO-1 циклов.
Далее подсчитаем число циклов декремента регистра CounterHi. Т.к. каждый декремент этого регистра производится только после обнуления регистра CounterLo, то при первом декременте получим:

3* DELAYLO -1+3

Последующие декременты будут выполняться за (256*3-1+3)(DELAYHI -1)-1 циклов.

Команды инициализации регистров, входа в подпрограмму и выхода из нее занимают 8 циклов. Добавив их к общему числу, и обозначив общее число как N, получим искомую зависимость:

N = (3* DELAYLO-1+3) + ((256*3-1+3)( DELAYHI-1)-1) + 8

Или:

N = 3* DELAYLO + 770* DELAYHI – 761

Если в эту формулу подставить константы из программы, то получится N=4096, что и должно быть. Произведем теперь обратную операцию – рассчитаем константы, требующиеся для заданного числа циклов N. Для этого приведем формулу к виду:

(N + 761) / 770 = 3* DELAYLO /770+ DELAYHI

Слагаемое «3* DELAYLO /770» меньше единицы, поэтому для определения старшего байта счетчика его можно отбросить. В результате получится:

DELAYHI = (N + 761) / 770

Далее, зная значение DELAYHI, находим DELAYLO:

DELAYLO = (N + 761 – DELAYHI*770) / 3

Приняв N = 4096, получим DELAYHI = 6, DELAYLO = 79, что соответствует нашей программе.
Возьмем теперь N = 3859. При этом получатся следующие результаты: DELAYHI = 6 и DELAYLO = 0. Задайте эти значения в программе test.asm в константах DELAYLO и DELAYHI. Проассемблируйте ее и проверьте длительность выполнения подпрограммы Delay05 аналогично тому, как это делалось в предыдущих выпусках. Она получится равной 4627 циклам, т.е. на 768 больше, чем 3859. Это было вызвано тем, что нуль, который записывается в CounterLo, соответствует значению 256, вследствие чего добавляется 768 циклов, требуемых для обнуления CounterLo при первом проходе подпрограммы. Из этого следует, что после расчета констант по приведенным выше формулам необходимо выполнить следующую логическую операцию: если DELAYLO равен нулю, то декрементировать DELAYHI.
Теперь представим, что в какой-то программе имеется несколько аналогичных подпрограмм. Константы для них рассчитаны для работы с какой-то определенной тактовой частотой. Иногда возникает такая ситуация, когда частоту генератора или время задержки необходимо изменить. При этом константы придется пересчитывать заново. Это может быть достаточно громоздкая работа, особенно, если констант много. Однако в ассемблере можно определять константы формулами, а самостоятельно задавать только исходные параметры (такие как частоту генератора или время задержки). Сделаем это.
В программе test.asm замените определение констант для пп Delay05 на приведенный ниже текст:
CODE
;Вычисление констант для подпрограммы задержки.
CLOCK  EQU .32768  ;Частота генератора (Гц)
TIME  EQU .500  ;Время задержки (в мсек)

N  EQU ((CLOCK/.4)*TIME)/.1000;Вычисление числа циклов
DELAYHI_TEMP EQU (N+.761)/.770;Вычисление значения для старшего байта
DELAYLO EQU (N+.761-DELAYHI_TEMP*.770)/.3;Задание константы для младшего;байта задержки
if  (DELAYLO==0)  ;
DELAYHI  EQU  DELAYHI_TEMP-1;Задание константы для старшего байта задержки
     ;при младшем байте равном нулю
else    ;
DELAYHI  EQU  DELAYHI_TEMP;Задание константы для старшего байта задержки
     ;при младшем байте не равном нулю
endif    ;

Вышеприведенный код позволяет автоматически вычислять константы для подпрограммы задержки при ассемблировании. В нем после директивы EQU вместо чисел заданы формулы. В константах CLOCK и TIME указываются частота генератора и требуемое время задержки. Далее вычисляется число машинных циклов N. Затем по приведенным выше формулам производится расчет константы DELAYHI_TEMP (вместо DELAYHI) и DELAYLO. Далее с помощью директив if… else… endif в случае равенства DELAYLO нулю константе DELAYHI присваивается уменьшенное на единицу значение DELAYHI_TEMP, а в противном случае просто значение DELAYHI_TEMP. Обратите внимание на то, что все числа заданы в десятичном формате. Если по ошибке не поставить точку перед числом, то ассемблер воспримет это число как шестнадцатеричное.
Обратите внимание, что точность полученной задержки будет находиться в пределах трех циклов. Если нужна задержка с точностью до цикла, то автоматически вычислять не получится. В этом случае требуемая точность достигается добавлением к подпрограмме одной или двух команд «nop» (ее длительность равна одному циклу и никаких действий эта команда не производит).
В следующем выпуске мы напишем программу, которая будет выполнять те же действия, что и предыдущая, но с использованием прерываний.
Этот выпуск рассылки будет посвящен прерываниям.
Итак, что же такое прерывание? Прерыванием можно назвать процесс, который временно останавливает текущее выполнение программы с целью реакции на какое-то асинхронное событие. Таких событий (источников прерываний) может быть несколько. Например, в PIC16F84A их всего 4, а в PIC16F877 уже 14. На практике все источники прерываний одновременно не задействуются, а используются только те, которые действительно необходимы. К примеру, прерывание может вызвать фронт сигнала на входной линии контроллера, переполнение таймера, прием байта по последовательному порту, и т.д. Однако, независимо от источника, работа с прерываниями происходит практически одинаково.
Разрешение/запрещение и распознавание прерываний в PIC16F84A производится через регистр INTCON, а в более мощных контроллерах так же через регистры PIE1, PIE2, PIR1, PIR2. На данный момент мы будем для простоты рассматривать PIC16F84A. Кстати, возможности прерываний имеются не во всех PIC – контроллерах, в серии PIC16C54xx они отсутствуют.
Для того, что бы разрешить какое-то прерывание, необходимо установить соответствующий бит регистра INTCON. Например, для разрешения прерывания по переполнению таймера необходимо установить бит TOIE (INTCON,5), а для разрешения прерывания по фронту на RB0 устанавливается бит INTE (INTCON,4). Помимо этого необходимо установить бит GIE (INTCON,7) разрешения всех незамаскированных прерываний.
По наступлению события прерывания нормальное выполнение программы будет остановлено и программа перейдет на адрес ПЗУ 0x004, который называется вектором прерывания. По этому адресу и должна находится подпрограмма обработки прерывания. При этом адрес возврата сохраняется в стеке. У PIC – контроллеров вектор прерывания один для всех источников, поэтому данная подпрограмма должна сама различать события, вызвавшие прерывание, если разрешено более одного прерывания. Определение источника прерывания производится путем опроса соответствующих флагов прерывания, которые устанавливаются по наступлению прерывания. Например, по переполнению таймера будет установлен флаг TOIF (INTCON,2), а по прерыванию по фронту на RB0 – флаг RBIF (INTCON,0).
Каковы – же отличия подпрограммы обработки прерываний от обычной подпрограммы? Их несколько:
1.По входу в эту подпрограмму необходимо сохранить значения регистров W и STATUS, т.к. в процессе обработки прерывания они скорее всего будут изменены.
2.Так же необходимо сохранить и другие регистры, которые могут использоваться как основной программой, так и подпрограммой обработки прерывания.
3.Подпрограмма должна сбросить флаг прерывания, который установлен по наступлению события прерывания.
4.По выходу из подпрограммы необходимо восстановить сохраненные ранее регистры.
5.Возврат осуществляется командой retfie, а не return.

Напишем теперь программу, использующую прерывания. Пусть она выполняет те же функции, что и предыдущая, т.е. зажигает и гасит светодиод с частотой 1 Гц. Напомню, что в предыдущей программе измерение временного интервала 0,5 сек производилось вызовом соответствующей подпрограммы задержки Delay05. В данном случае для этой цели мы воспользуемся встроенным таймером TMR0. Если к нему подключить предделитель с коэффициентом 16, то при тактовой частоте 32768 Гц его счет будет происходить за 0,5 сек.
Таймер представляет собой обычный восьмибитный регистр-счетчик, который можно программно считывать или записывать. На его вход можно подавать либо внутреннюю тактовую частоту, либо внешний сигнал. Так же имеется возможность подключения предделителя с коэффициентом деления 2 - 256, который напрямую не может быть считан или записан. Конфигурация TMR0 осуществляется через регистр OPTION. В нем битом 5 производится выбор тактовой частоты таймера (0 – внутренняя частота контроллера, 1 – внешний сигнал со входа RA4), битом 4 – фронт его инкремента (0 – передний, 1 - задний), битом 3 подключается предделитель (0 – предделитель подключен к таймеру, 1 – предделитель подключен к сторожевому таймеру), а битами 0-2 задается коэффициент деления предделиеля.
Ниже приводится текст программы. Сохраните данную программу под названием tmr.asm, создайте новый проект и подключите программу к нему.
CODE
#include p16f84a.inc
;Описание используемых в программе регистров общего назначения:
 cblock 0x0C
 W_Temp;байт для сохранения значения регистра W при прерывании
 STATUS_Temp;байт для сохранения значения регистра STATUS при прерывании
 endc
;Константы инициализации регистров специального назначения:
INIT_PORTA EQU b'00000000';Все линии на выход
INIT_PORTB EQU b'00000000';
INIT_OPTION EQU b'00000011';Предделитель включен перед таймером, Кдел=16
INIT_INTCON EQU b'10100000';Разрешено прерывание при переполнении таймера
 call Init  ;Инициализация
 goto $  ;Зациклить программу
int_point org 0x004;Вектор прерывания
 movwf W_Temp;Сохранение значений регистров W и
 swapf STATUS,0;STATUS
 movwf STATUS_Temp;
 bcf INTCON,2;Сброс флага переполнения таймера
 btfss PORTB,0;Если светодиод горит, выключить его
 goto On  ;Если он не горит, то включить
 bcf PORTB,0;
 goto retint;
On  ;
 bsf PORTB,0;
retint
 swapf STATUS_Temp,0;Восстановление сохраненных значений
 movwf STATUS;регистров W и STATUS
 swapf W_Temp,1;
 swapf W_Temp,0;
 retfie  ;Возврат из прерывания
;Инициализация
Init
 bsf STATUS,RP0
 movlw INIT_PORTA
 movwf TRISA^80h
 movlw INIT_PORTB
 movwf TRISB^80h
 movlw INIT_OPTION
 movwf OPTION_REG^80h
 bcf STATUS,RP0
 movlw INIT_INTCON
 movwf INTCON
 clrf STATUS
 return
 end

Проассемблируйте программу и нажмите F6. Как видно из текста программы, первой исполняющейся командой будет «call Init», т.е. будет вызвана подпрограмма инициализации. Ее код ничем не отличается от такой же подпрограммы в программе test.asm. Отличие состоит в инициализируемых значениях для регистров INTCON и OPTION. В прошлой программе в INTCON записывался нуль, тем самым запрещая прерывания, а в OPTION было вообще все равно, что записывать, т.к. таймером мы не пользовались. Сейчас в INTCON записывается b’10100000’, т.е. разрешаются прерывания при переполнении таймера. А в OPTION – b’00000011’, т.е. к таймеру подключается предделитель с коэффициентом деления 16. Таким образом, после инициализации программе больше нечего делать, кроме как ждать наступления прерывания. Поэтому следующая команда зацикливает программу – «goto $». Знак $ является директивой взятия адреса текущей команды ассемблера. При ассемблировании вместо него будет подставлен адрес адрес команды «goto» в ПЗУ контроллера, т.е. вместо «goto $» можно было бы написать:
CODE
metka
 goto metka

Далее начинается подпрограмма обработки прерывания. Директива «org» присваивает метке «int_point» адрес 0х004. Таким образом, команда «movwf W_Temp», находящаяся на этой метке будет расположена по адресу 0x004. Поэтому по возникновению прерывания эта команда будет исполнена первой.
Обратите внимание на команды, которыми производится сохранение и восстановление регистров W и STATUS. Для этого нужно задействовать только те команды, которые не вызывают изменения битов регистра STATUS, иначе в момент сохранения или восстановления значения этого регистра, оно может быть изменено. Кстати, в данной программе сохранять вообще ничего не нужно, т.к. она все время находится в бесконечном цикле и в основном своем теле никаких действий не производит. Сохранение W и STATUS здесь приведено лишь для примера.
После сохранения этих регистров, производится сброс флага переполнения таймера TOIF (INTCON,2), а затем в том случае, если светодиод включен, он выключается и наоборот, если выключен – включается. Далее восстанавливаются значения регистров W и STATUS, и производится выход из подпрограммы командой «retfie».
Откройте окно «Stopwatch», создайте окно «Watch_1» и поместите в него регистр таймера TMR0. Нажмите несколько раз F7 и Вы увидите, как происходит инкремент таймера. Т.к. на таймер подается внутренняя тактовая частота процессора через предделитель, инкремент TMR0 будет производиться через 16 циклов внутренней частоты контроллера.
Поставьте точку останова на команде «movwf W_Temp». Нажмите F9. После остановки программы сбросьте счетчик времени в окне «Stopwatch» и опять нажмите F9. Когда программа опять остановится, число циклов будет равно 4096. Измерьте время еще несколько раз. У вас будет получаться разный результат: 4095, 4096 и 4097 циклов. Это вызвано тем, что исполнение команды «goto $» занимает два цикла, а момент возникновения прерывания может попасть между этими циклами, что вызовет сдвиг в ту или иную сторону.

Категория: Счетчик TMR0 | Добавил: Олег (27.04.2013)
Просмотров: 1731 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа
Поиск
Друзья сайта
Статистика

Онлайн всего: 1
Шпионов 1
Пользователей: 0

Copyright MyCorp © 2024