Kea14

Переход в раздел "Готовим инструменты"

Как изготовить программатор "PonyProg" и работать с ним

Программатор PonyProg, на мой взгляд, является одним из лучших пограмматоров, доступных для самостоятельного изготовления.
Он поддерживает большое количество типов микроконтроллеров и микросхем памяти последовательного типа, прост в изготовлении, надежен в работе, не содержит дорогих или малодоступных радиодеталей и проверен в деле многими радиолюбителями.
Конечно же, существует масса других программаторов, но, как говорится, "старый конь борозды не испортит".
Программа PonyProg (создана итальянцем Клаудио Ланконелли), обслуживающая программатор, с моей точки зрения, также заслуживает всяческих похвал.
Работает надежно, "ругается" только по делу, проста и понятна в работе.
Для того чтобы программатор заработал, необходима поддерживающая его программа.
Поэтому, если Вы решили сделать такой программатор, Вам необходимо закачать файлы архивов программы PonyProg на жесткий диск Вашего компьютера.
Закачка архивов русской версии программы PonyProg на жесткий диск Вашего компьютера.
Необходимо закачать три архива. Порядок закачки не имеет значения.
Их Вы найдете на моем сайте, если щелкните по ссылке "Как изготовить программатор PonyProg и работать с ним" на главной странице сайта.
Названия этих архивов:
Архив ponyprogV205a.zip (585 кб.)
Архив PonyProg2000-Russian.zip (170 кб.)
Архив Pony.zip (102 кб.)
Установка русской версии программы "PonyProg"
После того, как Вы скачаете программу на жесткий диск Вашего компьютера, Вы увидите перечисленные выше архивы в том месте, которое Вы выбрали.
Создайте в папке "Program Files" диска С (или в любом удобном для Вас месте жесткого диска) папку PonyProg и разархивируйте в нее архивы ponyprogV205a.zip и Pony.zip (русификатор PonyProg2000-Ruussian.zip пока разархивировать не нужно, иначе, вместо русской, можно получить английскую версию).
После этого в папке PonyProg Вы увидите установочный файл программы: setup.exe и 10 файлов с расширением .gif .
Сначала необходимо установить английскую версию программы:
щелкните по файлу setup.exe, после чего запускается стандартная процедура установки программы.
Просто соглашайтесь со всем, что Вам будет предлагаться и не забудьте, когда Вам будет предложено выбрать место установки программы, указать папку PonyProg, о которой говорилось выше.
Когда установка английской версии программы закончится, в главном меню появится строка PonyProg.
Далее производится русификация. Для этого необходимо разархивировать архив PonyProg2000-Russian.zip в ту же папку PonyProg.
В процессе разархивации появится окно с вопросом о том, стоит ли заменить файл PonyProg.exe английской версии программы на файл русской версии программы? Жмите на "Да".
На этом процедура установки русской версии программы PonyProg закончена и с ней можно работать.
Архивы, после разархивирования, можно либо вообще удалить с жесткого диска Вашего компьютера (если Вы уверены, что в будущем они Вам не понадобятся), либо переправить их на дискету (самый лучший вариант), если предполагается их использовать в дальнейшем, либо оставить все как есть.

Изготовление программатора PonyProg

Принципиальную схему программатора можно скачать: здесь.

Это файл с расширением .pdf, который открывается в "Акробате".
Программатор состоит из базового модуля и сменных модулей. Базовый модуль имеет внешний разъем, к которому, в зависимости от типа микроконтроллера или микросхемы памяти, подключается тот или иной сменный модуль.
Базовый модуль
Найдите на принципиальной схеме схему с надписью Base board. Это и есть базовый модуль.
Можно собрать и его, но я могу порекомендовать Вам собрать то же самое, но на отечественных радиодеталях (лично я так и сделал). Хлопот гораздо меньше и работает он ничем не хуже чем исходный базовый модуль.

Отечественную версию базового модуля Вы можете скачать: здесь.

На резисторах R4,R5,R6 и стабилитронах VD3,VD6,VD7 собраны три одинаковых преобразвателя уровней (простейшие параметрические стабилизаторы), которые ограничивают амплитуду импульсов, поступающих с COM - порта компьютера, до безопасного для программируемого радиоэлемента уровня.
Так как программируемые радиоэлементы запитываются напряжением 5V, то уровень ограничения должен быть не выше этого значения.
Поэтому в качестве стабилитронов VD3,VD6,VD7 необходимо использовать стабилитроны КС147А с напряжением стабилизации 4,7V (или, в крайнем случае, КС139А).
На диодах VD1,VD2,VD5 и конденсаторе С1 собрано устройство управления составным транзисторным ключем на транзисторах VT1 и VT2.
Если на анодах VD1,VD2,VD5 одновременно будет присутствовать нулевой уровень (например, если системный блок компьютера выключен или COM - порт не подключен), составной транзисторный ключ на транзисторах VT1 и VT2 закрывается и напряжение питания 5V на программируемую микросхему не подается.
На транзисторе VT3 собран ключевой каскад управления исполнительными модулями.
На микросхеме DA1 собран стандартный стабилизатор напряжения 5V.
К разъему XT1 подводится постоянное напряжение 12V от внешнего источника питания. Удобнее всего (лично я сделал так) иметь внутренний сетевой источник питания, смонтировав его в корпусе базового блока.
Рекомендую повысить величину выходного постоянного напряжения сетевого блока питания с 12V до 14,0 - 14,5V. Причина этого будет изложена ниже.
При помощи разъема XT2 базовый модуль соединяется с COM-портом компьютера, то есть, на этот разъем поступают сигналы управления, вырабатываемые компьютером с помощью программы PonyProg, в процессе работы с микроконтроллером или микросхемой памяти (контакты 6 или 7 разъема), а также импульсные последовательности синхронизации и записываемые или считываемые импульсные последовательности (контакты 3 и 4 разъема).
Обмен данными между компьютером и микроконтроллером (или микросхемой памяти) осуществляется по шине данных последовательного типа (именно поэтому PonyProg является программатором последовательного типа).
При помощи разъема XT3 к базовому модулю подключается тот или иной сменный модуль.
Примечание: для обеспечения соответствия принципиальной схемы отечественной версии базового модуля и принципиальной схемы "родного" (то, что Вы видите в "Акробате") базового модуля по подключению цепей к разъему сменных модулей, в принципиальной схеме отечественной версии базового модуля нужно "перебросить" "корпусной" провод с 8-го контакта разъема XT3 на 10-й контакт этого же разъема (или поменять местами столбцы разъема XT3 с названиями "Контакт" и "Цепь").
Транзистор VT3 отечественной версии базового модуля задействуется при работе с м/контроллерами AT90Sxx, а при работе с м/контроллерами PIC16Fxx не задействуется.
По этой причине, 2-й контакт разъема J8 сменного модуля ПИКов, внутри этого сменного модуля, ни к чему не подключен ("висит").
Принципиальную схему базового модуля можно и нужно упростить, исключив из него составной ключ на транзисторах VT1,VT2, резисторах R2,R3,R8 и устройство управления им, собранное на диодах VD1,VD2,VD5 и конденсаторе С1.
Какого-то большого практического смысла от введения разработчиками программатора в принципиальную схему базового модуля перечисленных выше устройств, лично я не вижу, так как ничего страшного для программируемой микросхемы не произойдет, если она в процессе работы программатора будет запитываться постоянно, либо будет запитываться при выключенном системном блоке компьютера или при отсутствии электрического соединения между базовым модулем и COM - портом.
Наличие стабилитронов и низкое входное сопротивление ключа, собранного на транзисторе VT3, практически полностью исключает (кроме совсем уж экстраординарных случаев) выход микросхемы из строя за счет перегрузок по ее входам (из-за превышения предельно допустимых значений входных уровней).
Если возникнет необходимость в снятии с нее питающего напряжения 5V, нужно просто выключить сетевой блок питания.
В своем программаторе я сделал именно так и абсолютно никаких осложнений это не вызвало, а конструкция базового модуля существенно упростилась.
Поэтому рекомендую исключить из принципиальной схемы VT1,VT2,VD1,VD2,VD5,C1, а между точками подключения коллектора и эмиттера удаленного транзистора VT1 поставить перемычку.
После этого принципиальная схема базового модуля станет настолько простой, что ее сможет собрать даже начинающий радиолюбитель, причем, в этом случае, вовсе не обязательно делать печатную плату, базовый модуль с навесным монтажом будет работать не хуже.
Сетевой блок питания - простейший: силовой трансформатор и диодный мост (я использовал КЦ407 из-за его малых размеров, подойдут и другие выпрямительные диодные мосты или можно собрать его на диодах).
Так как используется сетевой блок питания, необходимо увеличить емкость конденсаторов С2 и С3.
В своем базовом модуле я установил вместо номиналов, указанных в отечественной версии базового модуля (С2 - 0,1мкф, С3 - 47мкф/16v), следующие номиналы: С2 - 500мкф/25v, C3 - 1000мкф/16v.
Такие значения емкостей выше тех значений, которые обеспечивают нормальную работу программатора, но, во-первых, "кашу маслом не испортишь", а, во-вторых, такие значения емкостей обеспечивают защиту от кратковременных резких колебаний напряжения в сети 220V, что немаловажно. Так что исходите из принципа "чем больше - тем лучше" с учетом приемлемых габаритов и стоимости этих конденсаторов.
Силовой трансформатор - малогабаритный (например, от сетевого адаптера калькулятора). Основное требование к нему: он должен обеспечивать на входе стабилизатора DA1 постоянное напряжение 14,0 - 14,5V (можно до 15,0V), а не 12V, как это указано в схеме.
Объяснение этому следующее: в сменном модуле, при помощи которого осуществляется программирование PIC контроллеров используется параметрический стабилизатор на 12,5 - 13,0V.
Естественно, для его нормальной работы требуется напряжение выше, чем 12,5 - 13,0V.
В этом смысле, напряжение в диапазоне 14,0 - 14,5V, подаваемое на вход параметрического стабилизатора и на вход стабилизатора на микросхеме DA1, является наиболее оптимальным, так как при этом обеспечивается указанное выше превышение при сохранении потребляемой от блока питания мощности на достаточно низком уровне, что актуально для маломощных сетевых трансформаторов.
Микросхема DA1 может работать без радиатора, но при использовании металлического корпуса базового модуля, что я Вам рекомендую (не стоит пренебрегать экранировкой), ее можно и нужно механически закрепить в любом удобном месте корпуса.
Разъем XT3 закрепляется на корпусе базового модуля таким образом, чтобы было удобно вставлять в него сменные модули.
Место установки сетевого тумблера включения блока питания, индикаторного светодиода VD4 и вывода из модуля сетевого шнура - на Ваше усмотрение.
Рекомендую также убрать разъем XT2 и распаять провода соединительного кабеля (между базовым модулем и COM-портом компьютера) на плате базового модуля, зафиксировав этот кабель хомутиком внутри базового модуля, так как лишние разъемные соединения не желательны.
Длина этого кабеля не должна превышать 50см. (чем меньше - тем лучше). Желательно, чтобы он был экранированным.
На другом конце кабеля распаивается стандартный 9-штырьковый компьютерный COM - разъем ("папа") по образу и подобию распайки разъема XT2 (см. принципиальную схему).
В дальнейшем соединительный кабель будет подключаться к свободному COM - порту, выбирать который Вы будете в программе PonyProg (об этом - далее).
Существуют также схемы подключения базового модуля к LPT - порту, в том числе и с использованием микросхем - буферов.
Советую людям, которые ценят свое время и труд, с ними не связываться, а использовать простой, дешевый, надежный и проверенный в работе вариант, который предлагаю я.
Сменный модуль для работы с PIC контроллерами
Для того чтобы увидеть принципиальную схему этого сменного модуля, щелкните по расположенной выше ссылке "Принципиальную схему программатора можно скачать: здесь" (по слову "здесь").
После того как "Акробат" раскроет файл, в правом нижнем углу найдите принципиальную схему с названием PIC adapter. Это и есть описываемый сменный модуль.
Увеличьте его лупой "Акробата" так, чтобы были видны все детали схемы.
U9,U18,U17 - соответственно: 18,28,8 - выводные панельки для микросхем типа DIP (самые распространенные).
На транзисторах Q2 и Q5 собран транзисторный ключ. Его функция: если на 3-м выводе разъема J8 присутствует "1", то транзистор Q5 открывается и напряжение 14,0 - 14,5V с 7-го вывода разъема J8 подается на вход стабилизатора, а если "0", то не подается.
Стабилизатор - простейший и собран он на стабилитроне Z4 и резисторе R10. С его выхода напряжение около 13V поступает (или не поступает) на 4-й вывод панельки U9.
Для повышения устойчивости работы ключа, к его выходу подключена активно - емкостная нагрузка R19,C13.
Резистор R12 ограничивает базовый ток транзистора Q2.
Резисторы R13 и R14 образуют делитель напряжения, а напряжение на резисторе R14 управляет транзистором Q5.
Эти же резисторы являются суммарной коллекторной нагрузкой транзистора Q2, а резистор R13, кроме этого, еще и ограничивает до безопасного уровня базовый ток транзистора Q5.
На транзисторе Q3 также собран ключ, управляемый сигналом, поступающим с 5-го вывода разъема J8.
На схеме Вы также увидите батарею 9V и переключатель JP1. По замыслу разработчика они необходимы для того, чтобы организовать режим "батарейной" запитки ключа на транзисторах Q2 и Q5.
Я не случайно поставил кавычки в слове "батарейной", так как, в этом случае (выводы 1 и 2 переключателя JP1 замкнуты), на вход ключа подается не 9V только от батареи (этого мало для нормальной работы стабилизатора на Z4 и R10), а суммарное напряжение 14V (9V от батареи + 5V с выхода стабилизатора базового модуля). По этой причине не совсем понятен сам смысл такого усложнения принципиальной схемы, ведь, если не будет напряжения в сети 220V, напряжение на выходе ключа будет значительно ниже нормы даже если предположить, что компьютер при этом не выключился.
Единственное разумное объяснение этому - то, что от такого нововведения повысится помехоустойчивость при записи или считывании информации, но это может быть ощутимо только при использовании совсем уж "слабосильного" силового трансформатора, напряжение на вторичной обмотке которого понижается от малейшего увеличения нагрузки.
Если Вы примените силовой трансформатор от сетевого адаптера, расчитанного на нагрузку от 50 мА. и выше (а таких - большинство), смело убирайте батарею и переключатель JP1, от этого ничего не ухудшится.
Гораздо проще и удобнее собрать этот сменный модуль на отечественных радиодеталях.
Лично я так и сделал. Советую сделать это и Вам. Модуль на отечественных радиодеталях работает ничем не хуже своего "родителя" и с ним меньше хлопот.
Изменения, вносимые при этом в схему:
- заменить транзистор Q5 с ВС557 на КТ361 (можно ставить с любой буквой, у меня стоит "Д")
- заменить транзистор Q2 и Q3 с ВС547 на КТ3102 (можно ставить с любой буквой, у меня стоит "А")
- стабилитрон Z4: поставить Д814Д или любой другой с напряжением стабилизации в диапазоне 12,5 - 13,0V. Это "ответственный" радиоэлемент, поэтому не поленитесь подобрать его по напряжению стабилизации.
- увеличить номинал R16 с 1 ком. до 2,2 ком.
- исключить из принципиальной схемы батарею 9V и переключатель JP1, а после этого подключить точку соединений: эмиттера транзистора Q5, резистора R14 и конденсатора С21 к 7-му выводу разъема J8.
- остальное - без изменений.
Принципиальную схему сменного модуля можно упростить, исключив из нее панельку U17.
Дело в том, что микроконтроллеры PIC12C50x в радиолюбительских конструкциях (и не только) применяются достаточно редко в связи с тем, что их "общение" с внешними устройствами происходит по не совсем удобной для разработчика последовательной шине данных.
После того, как базовый модуль и сменный модуль для работы с PIC контроллерами будут изготовлены, и программа PonyProg установлена, можно работать с PIC контроллерами.
Так как в мои планы не входит описание работы с другими типами микропроцессоров, я на этом и ограничусь.
Если кто-то работает с другими типами микропроцессоров, изготовьте соответствующие модули (см. принципиальную схему), они еще проще, чем описанный выше модуль.
Можно собрать их в полном соответствии с принципиальными схемами, а можно и убрать из них панельки тех типов микропроцессоров, с которыми Вы работать не будете, а оставить только нужные.
Принципиальные схемы базового модуля и сменного модуля для программирования PIC контроллеров. Печатная плата сменного PIC-модуля.
Доработанный, в соответствии с приведенными выше изменениями, вариант принципиальной схемы базового модуля и сменного модуля для программирования PIC контроллеров, которые я продолжительное время использую в своей работе:
Файл этой картинки: здесь.
Примечание: в схеме базового модуля не указан контрольный светодиод (контроль наличия U=5v.) с его гасящим резистором.
Для того чтобы не перегружать маломощный сетевой трансформатор, желательно выбрать номинал этого гасящего резистора не менее 1 ком.
Принципиальная схема базового модуля настолько проста, что я собрал ее с использованием навесного монтажа.
Мало того, если предполагается работать только с PIC контроллерами, то транзистор VT4 и резисторы R11 и R12 из принципиальной схемы базового модуля можно исключить.
Двухсторонняя печатная плата сменного модуля для программирования PIC контроллеров
разработана мной в P-CAD, переведена в формат .bmp, отредактирована и переведена в формат .gif .
Размер платы 95х50 мм. Показана в увеличенном виде.
Этот сменный модуль я длительное время использую в работе и замечаний по его работе у меня нет.
Применен поверхностный монтаж радиодеталей и DIP-панелек.
Выводы DIP-панелек отгибаются на 90 градусов наружу и в таком виде они и припаиваются к контактным площадкам поверхностного монтажа.
DIP-панельки обычные "ширпотребовские". Если возникнет необходимость в их замене на более "свежие", то, при таком способе монтажа, это делается довольно-таки просто.
Остальные радиодетали подпаиваются к контактным площадкам таким же образом.
Серые кружочки - переходы с одной стороны платы на другую (запаиваются перемычки).
"Мама" разъема МРН14 установлена в базовом модуле, а "папа" - на плате сменного PIC-модуля.
Печатная сменного PIC-модуля с наложением всех слоев:
Файл этой картинки: здесь.
Расположение радиодеталей:
Файл этой картинки: здесь.
Картинки сторон печатной платы PIC-модуля "на все случаи жизни" (для различных технологий изготовления печатных плат) с "привязкой" к размеру 95х50 мм.
Распечатав эти картинки на принтере, Вы получите качественные рисунки печатной платы PIC-модуля размером 95х50 мм.
Сторона установки радиодеталей:
Файл этой картинки: здесь.
То же самое в зеркальном отображении:
Файл этой картинки: здесь.
Обратная сторона:
Файл этой картинки: здесь.
То же самое в зеркальном отображении:
Файл этой картинки: здесь.
Работа с PIC контроллерами в программе PonyProg
В качестве примера я буду использовать PIC16F84A.
При выключенном компьютере, подключите разъем базового модуля к разъему свободного COM - порта. Обычно это COM2.
Вставьте PIC16F84A в панельку сменного модуля, а затем подключите сменный модуль к базовому модулю (вставьте в разъем).
Подключите базовый модуль к сети.
Включите компьютер.
Откройте программу PonyProg, щелкнув по строке PonyProg в главном меню.
После этого Вы увидите заставку программы с симпатичной лошадкой. Ничего особо интересного на ней нет, так что щелкайте по OK.
После этого вы увидите окно программы и два предупреждения о необходимости запуска калибровки и настройки оборудования перед началом операций чтения и записи.
Примите к сведению эти предупреждения и уберите их, щелкнув по OK.
Для удобства, раскройте окно программы на весь экран обычным "Виндовским" способом.
В верхней правой части окна программы, на одной линии, Вы увидите две строки: выбора вида и типа микроконтроллера или микросхемы памяти.
Они снабжены кнопками, щелкнув по которым, в выпадающем списке можно просмотреть те виды и типы микроконтроллеров и микросхем памяти, которые поддерживает программа PonyProg. Согласитесь со мной, список довольно-таки внушительный.
Выберете в левом списке PIC 16 micro, а в правом списке PIC16F84A.
Далее выполняем техническую процедуру настройки программы (то, о чем программа предупреждала ранее):
щелкните по кнопке Установки в главном меню программы PonyProg.
В выпавшем после этого списке, состоящем из двух пунктов, щелкните по пункту Настройка оборудования.
Раскроется окно Настройка платы программатора, в котором единственное, что нужно сделать - поставить точку в кружочке COM2 (если программатор подключен к COM2), остальное - по умолчанию.
В результате: в кружочках Последовательный и COM2 должны стоять точки, из выпадающего списка должна быть выбрана строка Si Prog API, а в списке Выбор полярности сигналов управления все квадратики должны быть чистыми (без галочек).
Затем щелкаете по кнопке Проверка, после чего раскрывается маленькое окошко Notice с надписью Тест ОК (все в порядке).
Закройте окошко Notice, щелкнув по OK и окно Настройка платы программатора, еще раз щелкнув по OK.
На этом настройка оборудования завершена.
Если в окошке Notice присутствует надпись Тест Ошибка, то необходимо убедиться в том, что порт COM2 свободен и базовый модуль программатора подключен именно к порту COM2.
Если это так, то нужно поменять полярность сигналов управления или выбрать в выпадающем списке другую строку, каждый раз проводя тест, до получения сообщения Тест ОК.
Естественно, что параметры настройки оборудования зависят от конкретного компьютера, но, в подавляющем большинстве случаев, настройки именно те, что были указаны выше.
Далее производим калибровку.
Для этого еще раз нужно щелкнуть по кнопке Установки в главном меню программы PonyProg и выбрать из выпадающего списка пункт Калибровка.
Программа сначала спросит разрешения на запуск калибровки. Нажмите Yes.
После этого программа PonyProg начнет "договариваться" с операционной системой о порядке их совместной работы. Я не наблюдал случаев, чтобы они не "договорились".
В результате, через определенное время, откроется окошко Notice с надписью Калибровка завершена. Закройте его, щелкнув по OK.
Программатор готов к работе.
Запись данных в PIC контроллер и считывание данных из него
В PIC контроллерах имеется два вида энергонезависимой памяти: память программ и память данных (EEPROM память). Запись в них можно производить многократно.
В память данных записываются фиксированные данные (константы) или результаты операций с числами, которые используются в ходе выполнения программы и которые необходимо сохранить после выключения питания.
В нее может также записываться информация об авторе программы, а также техническая информация (если в ней остается достаточно места, многие программисты так и поступают).
Объем ее не велик, для PIC16F84A он составляет 64 байта.
Объем памяти программ гораздо больше. Память программ PIC16F84A позволяет реализовать в ходе выполнения программы до 1024 команд (максимум), подавляющее большинство которых выполняется всего за один машинный цикл (1мкс, если применен кварц на 4 мГц).
Насколько понятно из самого названия, именно в память программ и записывается программа (вся или основной ее объем, если задействована память данных).
Следует также уяснить, что то, что находится в памяти программ микроконтроллера после записи и HEX - файл (расширение .HEX относится только к PIC контроллерам), это не одно и то же.
Файл с расширением .HEX содержит закодированную программу (результат ассемблирования текста программы), предназначенную для записи в память программ микроконтроллера после ее декодирования в программе, обслуживающей программатор.
Причина такой кодировки такая же, как и при архивации - обеспечение удобства при хранении и транспортировке файлов, и, по большому счету, кодировку программ для микроконтроллеров можно рассматривать как специфическую разновидность архивации.
Программа же в "чистом виде" (декодированная), появляется в памяти программ микроконтроллера после записи, а еще ранее (до записи) Вы ее увидите после открытия HEX - файла в программе PonyProg (см. ниже). В этом случае Вы видите не HEX - файл, а результат его декодирования.
"Чистый" HEX - файл можно увидеть только в специализированных программах (например: MPLAB).
Строго говоря, если в программе задействована память данных (EEPROM память), ее тоже нужно брать в расчет как, хотя и малую, но составную часть программы.
Приступаем к работе.
Напоминаю, что на этот момент выбран PIC16F84A, произведены необходимые установки, программатор подключен и в него вставлен PIC16F84A, в память программ и в память данных которого необходимо произвести запись.
Сначала нужно открыть HEX - файл.
В главном меню программы PonyProg щелкните по кнопке Файл и выберете из выпадающего списка строку Открыть файл с данными или щелкните по пиктограмме с желтой открытой папкой (при наведении на нее стрелки, появляется всплывающая подсказка Открыть файл с данными).
После этого раскрывается стандартное окно открытия файлов.
Перед просмотром файлов выберете из выпадающего списка Тип файлов (в нижней части окна открытия файлов) тип файлов с расширением .HEX.
Далее нужно отыскать то место (на жестком диске компьютера или на внешних носителях), где у Вас находится HEX - файл, который нужно открыть, и щелкнуть по нему, а затем - по кнопке Открыть.
Окно открытия файлов сворачивается и в окне программы PonyProg Вы увидите то, что необходимо записать в PIC контроллер.
Запись происходит следующим образом:
необходимо либо щелкнуть по пиктограмме Записать устройство (ориентируйтесь по всплывающей подсказке), либо по строке Записать все в Командах.
Дождитесь окончания процесса записи и проверки, в конце которого выдается сообщение об успешной записи.
Если выдается сообщение об ошибке записи, щелкните по пиктограмме с нарисованным на ней замком и в открывшемся окне битов конфигурации посмотрите, есть ли галочка в маленьком окошке с названием CP (бит защиты).
Если она стоит, то сначала запишите, а потом считайте установленные программой биты конфигурации (менять их или вносить какие-либо изменения не нужно).
После считывания убедитесь, что считано то же самое, что записано.
Эта процедура бывает необходимой в случае установки записываемой программой бита защиты (подробнее об этом - ниже), в других случаях ее проводить не обязательно.
После этого окно битов конфигурации нужно закрыть (щелкнуть по OK) и повторить процедуру записи.
Если при первой, после сборки программатора, попытке записи постоянно выдаются сообщения об ошибках записи, зайдите в Настройку оборудования (см. выше), откройте окно Настройка платы программатора и попробуйте изменить настройки по умолчанию, каждый раз после этого контролируя результат записи до получения сообщения об успешной записи.
Несколько слов о стирании (строка Стереть в Командах или пиктограмма с нарисованным на ней ластиком и всплывающей подсказкой Erase all the device to FF - установить во всех байтах FF):
перед записью производить стирание вовсе не обязательно.
Дело в том, что запись производится "поверх" старых данных и по всему объему памяти.
По этой причине, при записи новой программы, старая программа автоматически "уничтожается" без предварительного стирания.
По существу, единственным случаем оправданного применения стирания является тот случай, когда PIC контроллер передается кому-то, и при этом нужно, чтобы этот "кто-то" не смог считать последнюю записанную в него программу.
Если запись прошла успешно, то проблем с чтением данных из энергонезависимой памяти PIC контроллера, в подавляющем большинстве случаев, не будет.
Для того чтобы считать данные из энегонезависимой памяти PIC контроллера, нужно щелкнуть в Командах по строке Считать все или по пиктограмме с всплывающей подсказкой Считать устройство и дождаться окончания процесса считывания.
Если программатор в режиме записи работает надежно, то, в подавляющем большинстве случаев, это является гарантией того, что и считывание пройдет успешно.
Если, все-таки, Вы получите сообщение об ошибке, то повторите считывание еще раз.
Если Вы слишком часто получаете сообщение об ошибке при чтении (это относится и к записи), займитесь экранировкой соединения между базовым модулем программатора и COM - портом компьютера или сделайте его короче.
После окончания успешного процесса считывания, Вы не должны заметить никаких изменений в окне программы PonyProg.
Это говорит о том, что то, что, перед считыванием было записано, то и считано.
В окне программы PonyProg Вы увидите содержимое памяти программ, а в нижней его части (сделайте прокрутку) - содержимое памяти данных (EEPROM - памяти).
В комплексе, это и есть содержимое той программы, которую Вы записали.
О бите защиты.
После открытия HEX - файла, откройте окно установки битов конфигурации (см. выше) и обратите внимание на квадратик с надписью CP(бит защиты).
Если он пустой, то бит защиты не установлен и считывание (после записи) из PIC контроллера этой программы возможно (см. выше).
Если в нем стоит галочка, то бит защиты установлен программой.
В этом случае, во избежание выдачи программой PonyProg сообщений об ошибках записи, перед записью проведите процедуру записи - считывания битов конфигурации (см. выше) и, после этого, произведите запись.
При установленном программой бите защиты, считывание программатором (любым) данных из энергонезависимой памяти микроконтроллера не представляется возможным (считаются нули).
На работе программы внутри микроконтроллера это никак не отразится.
Если Вы решили тиражировать имеющуюся у Вас программу, в которой бит защиты не установлен, но не хотите, чтобы эта программа была растиражирована с копии (копий), Вы можете, перед записью программы в другой микроконтроллер, самостоятельно установить бит защиты.
Для этого нужно открыть окно битов конфигурации и поставить галочку в квадратике CP.
После этого необходимо произвести процедуру записи битов конфигурации, а затем - чтения битов конфигурации для того, чтобы убедиться, что бит защиты CP записался.
Далее - обычная процедура записи программы в PIC контроллер (см. выше). После ее завершения не забудьте произвести ее считывание из PIC контроллера для того, чтобы убедиться, что из его энергонезависимой памяти считываются только нули.
После этого Вы можете быть уверенным в том, что с этого PIC контроллера тиражировать Вашу программу нельзя.
Дополнительные возможности программы PonyProg
Основная из них - возможность редактирования текста программы на уровне изменения значения байтов.
Такого рода редактированием имеет смысл заниматься, если точно известны адреса редактируемых байтов и те их зачения, которые необходимо записать по этим адресам взамен "старых".
Осознанно работать таким образом с текстом программы крайне затруднительно, да и не нужно, так как для программистов существует множество программ - редакторов, с которыми работать гораздо проще и быстрее, так что практическая потребность в редактировании текста программы в PonyProg сведена, по-существу, к нулю, и я не советую (в первую очередь - начинающим) вообще "забивать себе этим голову".
Если кому-то интересно - откройте какой-нибудь HEX - файл и поредактируйте его содержимое, ничего сложного в этом нет, Вы в этом разберетесь сами без особого труда.
После открытия любого HEX - файла, в правой части окна программы PonyProg Вы увидите результат работы простенькй программы - раскодировщика, который переводит числовые значения байтов в стандартные символы.
В данной версии PonyProg, эта программа понимает только английскую раскладку (на месте русских букв будут прочерки).
Даже если бы она понимала и русскую раскладку, смысл ее существования достаточно туманен, так как перевод текста программы в стандартные символы имеет практический смысл только при написании программ в текстовых редакторах на стадии создания текста программы, а в программе - раскодировщике Вы увидите абракадабру, так как исполняемая часть программы (раскодированный HEX - файл) ни на один язык народов мира не переводится.
Что-то читаемое и осмысленное может быть только в позициях с младшими адресами памяти программ ("шапка" программы) и в памяти данных, если это читаемое и осмысленное заложено туда программистом.
Есть еще несколько "архитектурных излишеств", без которых вполне можно обойтись. Кому интересно и есть время - пощелкайте.
Практический смысл выбора, в качестве "рабочего инструмента", именно программатора PonyProg не в них, а в том, что при своей простоте, он прекрасно справляется со своей основной функцией и поддерживает большое количество микросхем.
В этом смысле он выгоден по соотношению "цена - качество" и удовлетворяет большую часть потребностей даже профессиональных программистов.
Примечание: приведенная выше информация, конечно же, не является детальной (я и не ставил себе такой задачи). Она расчитана на радиолюбителей, которым нужно, особо не вдаваясь в тонкости программирования, просто "зашить" программу в микроконтроллер или считать ее из него.

Дополнительная информация

Советую Вам познакомиться с информацией от Станислава Гетманского из г. Самара.
С учетом изложенных выше рекомендаций, это как раз то, что нужно для изготовления и настройки программатора PonyProg (для ПИКов), включая и рисунок печатной платы.
Эту информацию (файл с расширением .doc объемом 92 Кб.) Вы найдете: здесь.

Информацию по настройке программатора PonyProg Вы также можете получить, щелкнув: здесь.

Корабельников Евгений г. Липецк http://ikarab.narod.ru E-mail: karabea@lipetsk.ru

Переход в раздел "Готовим инструменты"


Программирование PIC контроллеров с "нуля"

Самоучитель по программированию PIC контроллеров для начинающих.

Автор: Корабельников Евгений Александрович г. Липецк 2005г.

Одна из главных бед начинающих программистов - отсутствие четко определенной последовательности в восприятии информации и ее "передозировка", связанная с чрезмерным желанием побыстрее достигнуть желанной цели.
Такого рода желание, конечно, похвально, но при отсутствии ясно выраженных приоритетов и способности, на первых порах, сознательно ограничивать объем воспринимаемой информации только самой действительно необходимой, оно играет с человеком злую шутку.
В результате - "бардак" в голове, дизориентация в потоках информации и, в худшем случае, сожаление о потраченном времени, хотя, по большому счету все не так сложно, как может показаться. Надеюсь Вас убедить в этом.
Информация будет предоставляться в определенной последовательности и по принципу "от простого к сложному".
Прошу придерживаться этой последовательности и не переходить к следующим разделам без уяснения предыдущих. Дело это неторопливое и не требует суеты.
Если будут возникать вопросы, то задавайте их в моей гостевой книге, ссылку на которую Вы найдете в конце этой WEB-страницы или по E-mail.
Самоучитель расчитан на начинающих, но при этом предполагается, что они, как минимум, знают основы цифровой техники.

"Дополнительно": здесь

Разделы

1. Готовим инструменты.

2. Что такое микроконтроллер, и как он работает.

3. Система команд PIC16F84A.

4. Что такое программа и правила ее составления. Пример создания программы
автоколебательного мультивибратора. Директивы.

5. Интегрированная среда проектирования MPLAB IDE и работа в ней.

6. Что дальше?

7. Пример создания программы (начало).

8. Пример создания программы (продолжение).

9. Работа в симуляторе. Отладка программы.

10. Как отследить выполнение программы

11. Прерывания. Стек. Пример разработки программы с уходом в прерывания.

12. Организация вычисляемого перехода. Работа с EEPROM памятью данных.

13. Флаги. Работа с флагами. Как работает цифровой компаратор. Перенос и заем.

14. Пример задействования флага С в трехбайтном суммирующем устройстве.
Циклический сдвиг. Операция умножения.

15. Введение в принцип построения подпрограммы динамической индикации. Косвенная
адресация.

16. Преобразование двоичных чисел в двоично-десятичные. Окончательное формирование
текста подпрограммы динамической индикации.

17. Принцип счета. Работа с таймером TMR0. Принцип установки групп команд счета в
текст программы.

18. Пример программы, включающей в себя группы подпрограмм 15, 16, 17 разделов.
Задания на разработку.

19. Разные "полезности".

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

"Самоучитель по программированию PIC контроллеров для начинающих" http://ikarab.narod.ru E-mail: karabea@lipetsk.ru


Kea_21

Переход к оглавлению

1. Готовим инструменты

Микроконтроллеры (и вообще все процессоры) изначально понимают только машинные коды, то есть некую совокупность нулей и единиц.
Те, кто представляет себе работу счетчиков, регистров, триггеров и т.д., сразу же поймет природу машинного кода.
Работать напрямую с машинными кодами при составлении программ и врагу не пожелаешь, для этого нужно обладать недюжинными способностями.
Для того, чтобы нормальные люди могли, без особого напряга заниматься составлением программ, и придуманы различные языки программирования.
Смысл всех их заключается в замене машинных кодов словами, сокращениями слов, абревеатурами и т. д., то есть тем, что человеком легко и осмысленно воспринимается и чем он может комфортно оперировать при составлении текста программы.
Таких языков великое множество, но нас интересует только самый простой из них - ассемблер.
Следует заметить, что слово "простой" относится прежде всего к набору его команд: количество их - минимально необходимое, и, тем не менее, вполне достаточное для решения самых сложных задач, но не к комфортному восприятию их человеком.
В большинстве своем, команды ассемблера являются либо сокращениями английских слов, либо набором первых букв английских словосочетаний, либо и тем, и другим.
Минимальный "джентльменский" набор ассемблера для ПИКов составляет 35 команд, реально же, наиболее часто, используются от 10 до 20 команд.
В дальнейшем настройте себя просто на тупое заучивание (на первых порах) всей этой английской абракадабры типа зубрежки (я учил немецкий язык, и ничего, освоил), не такая уж это и сложная задача. В дальнейшем Ваше образное мышление и зрительная память Вам помогут.
А выучить ассемблер очень даже стоит по причине того, что он, может быть, и не очень "удобоварим", но именно на этом языке пишутся самые компактные по объему, быстрые и надежные программы, и, по этой причине, серьезные программисты, а также классные хакеры, работают преимущественно в ассемблере.
Предупреждение: на этом этапе в ассемблер не лезть! Всему свое время. Пока достаточно общего представления.
Итак, мы выяснили, что программы для ПИКов составляются преимущественно в ассемблере.
Сам текст программы можно, в простейшем случае, написать в любом текстовом редакторе, например, в обычном Вордовском, но такого рода информацию в ПИК записывать нельзя, так как он понимает только машинные коды.
Следовательно, нужно преобразовать текст программы с языка ассемблер в машинные коды, то есть, необходимо так называемое ассемблирование исходного текста программы.
Вот здесь-то начинающие обычно и путаются: словосочетание "ассемблирование исходного текста программы" означает не перевод исходного текста программы на язык ассемблер (текст программы уже написан на языке ассемблер), а, наоборот, преобразование текста программы, написанной на языке ассемблер, в машинные коды, которые сначала соответствующим образом архивируются и помещаются внутрь специального файла с расширением .HEX (для удобства хранения и транспортировки машинных кодов), а затем разархивируются из HEX-файла и принимают свой исходный вид в программе, обслуживающей программатор, которая и записывает исходные машинные коды программы в ПИК.
Приведенное выше словосочетание, используемое сплошь и рядом, безусловно, не является удачным, обратите на это внимание и всегда имейте ввиду, что оно не отражает смысла происходящего, хотя я и буду употреблять его далее, так как оно является стандартным.
Естественно, для того чтобы создать HEX-файл, необходимо иметь специальную программу.
Самая лучшая программа такого рода - программа, созданная производителем ПИКов, то есть фирмой Microchip Technology Inc.
Представителем этой фирмы в России является ООО "Микро-Чип", которое имеет в Интернете свой сайт технической поддержки на русском языке http://www.microchip.ru
Лично я пользуюсь ее версией 5.70.40 и Вам советую, но можно установить и версию 5.70.00.
В дальнейшем я буду ориентироваться на версию 5.70.40.
Щелкнув здесь, Вы перейдете на сайт технической поддержки фирмы Microchip Technology Inc, где и можно скачать программу MPLAB версии 5.70.40.
В нижней части этой WEB-страницы Вы увидите ссылку на архивные файлы программы MPLAB версии 5.70.40.
Щелкните по ней, и на открывшейся WEB-странице Вы увидите файлы, которые нужно скачать.
Программа MPLAB является интегрированной средой разработки для ПИКов и содержит все необходимое как для написания и редактирования программы, так и для создания HEX-файлов, а также и для отладки программы.
Таким образом, необходимость в наличии отдельного текстового редактора для написания программы, отдельной программы - ассемблера для создания HEX-файла и отдельного отладчика программы (симулятора) отпадает, так как в MPLAB IDE все это уже есть (и даже более того). Ничего, кроме MPLAB IDE скачивать с указанного выше сайта, на данном этапе обучения, не нужно.
Не стоит также раньше времени забивать себе голову разборками с этой программой, так как она достаточно сложна (посмотрите на ее объем) и можно легко запутаться. Подойдет время, и я расскажу как с ней работать.
А пока установите MPLAB IDE на свой компьютер, убедитесь, что она встала не криво и на время про нее забудьте, так как для того, чтобы с ней работать необходимо основательно подготовиться, чем, в дальнейшем, мы и будем заниматься.
Следующий шаг - сборка программатора, так как HEX-файл программы, созданный в MPLAB, необходимо разархивировать, а результат разархивирования записать в ПИК.
Ничего проще и надежнее чем программатор PonyProg, подробное описание сборки которого Вы найдете на моем сайте, я предложить не могу, хотя, безусловно, имеются и другие "достойные" программаторы.

Информацию по сборке программатора PonyProg Вы найдете: здесь.

Следует учитывать, что программатор PonyProg (и подобные ему) лучше всего работает на медленных компьютерах старых выпусков, так как программа PonyProg создавалась под них.
При подключении программатора к современным быстродействующим компьютерам с "навороченными" операционными системами могут возникать конфликты типа "нестыковки" программы PonyProg с операционной системой или превышения предельно допустимой скорости обмена данными между компьютером и программируемым ПИКом, то есть программатор может просто не заработать. Самое лучшее решение - применение для этих целей компьютера с тактовой частотой до 200мГц и операционной системы Windows95/98.
Лично я сделал так: купил за 500руб. "древнюю старушку", обманул BIOS, пристегнув к родному винчестеру, "помощником", дополнительный винчестер на 8Гб, поставил Windows98 и включил "форсаж".
Получилось "дешево и сердито" и, прежде всего, по той причине, что при занятии программированием, создании печатных плат, вычерчивании схем и прочих радиолюбительских делах, особой скорости и не требуется, так как все эти занятия неспешны и особого смысла задействовать под это дело быстродействующие компьютеры нет.
В моей "старушке" стоит почти на 2Гб подобного рода программ, в том числе и довольно-таки мощных, и ничего, прекрасно работает.
С тем, что должно быть в наличии обязательно, мы разобрались, а теперь о полезных мелочах.
Скачайте с моего сайта (из раздела № 1 "Самоучителя...") эти две маленькие, но полезные и удобные программки:
HEX - калькулятор
Конвертор систем исчисления

Они настолько просты, что Вы без труда разберетесь, зачем они нужны. Чаще всего требуется конвертор систем исчисления.
Итак, все, что необходимо для занятия программированием PIC контроллеров у нас имеется. Пока что все это не более чем красивые и интригующие штучки не очень понятного назначения. Можете их на досуге рассмотреть, на что-нибудь понажимать, только, во избежание дальнейших недоразумений, не трогайте настроек по умолчанию.
Переходите к следующему разделу.

Переход к оглавлению


Kea_22

Переход к оглавлению

2. Что такое микроконтроллер, и как он работает

Прежде всего, микроконтроллер это процессор со всеми его атрибутами плюс встроенная память, что позволяет отказаться от внешней памяти программ и поместить программу в его энергонезависимую память, а это дает возможность создавать очень простые, в схемотехническом отношении, устройства, выполняющие, тем не менее, сложные функции.
Сердцем микроконтроллера является арифметико - логическое устройство (АЛУ).
Проще всего его представить в виде банального калькулятора, кнопками которого управляет программа, написанная на языке ассемблер.
Если вдуматься, то ничего особо сложного в самом механизме управления такого рода калькулятором нет.
И в самом деле, если нам нужно, например, сложить числа А и В, то в тексте программы сначала задаются константы А и В, а затем дается команда "сложить".
Пользователю совершенно не обязательно знать, что происходит с нулями и единицами (если только для общего развития), ведь калькулятор он на то и калькулятор, чтобы избавить пользователя от возни с машинными кодами и прочими неудобоваримыми штучками.
Когда Вы работаете с компьютером, Вам и не нужно детально знать, что происходит в дебрях операционной системы. Если Вы туда полезете, то с ума сойдете, а микроконтроллер, по сути своей, есть тот же самый компьютер, только очень простой.
Программисту только нужно детально знать, каким образом приказать машине сделать то, что необходимо для достижения задуманного. В этом смысле, аналогия с работой на компьютере - полная.
Микроконтроллер можно представить как универсальный набор многофункциональных модулей (блоков), рычаги управления которыми находятся в руках программиста.
Этих рычагов достаточно большое количество, и, естественно, их нужно освоить и точно знать, что произойдет, если дернуть (дать команду на языке ассемблер) за тот или иной рычаг.
Вот здесь -то нужно знать как "отче наше" каждую деталь и не жалеть на это времени. Только таким образом пустую болванку (незапрограммированый ПИК) можно заставить выполнять какие-то осмысленные действия, результат большей части которых можно проверить в симуляторе MPLAB (об этом - позднее), даже не записывая программу в ПИК.
Итак, переходим к "модульному" мышлению.
Любой микроконтроллер можно уподобить детскому конструктору, в состав которого входит множество всяких предметов, манипулируя с которыми, можно получить тот или иной конечный продукт.
Давайте с ними разберемся и "разложим все по полочкам".
В качестве примера я буду использовать один из самых распространенных PIC контроллеров PIC16F84A. Он содержит минимальный "джентльменский набор" модулей и лучше всего подходит для обучения начинающих программистов.
Энергонезависимая память
 Начнем с энергонезависимой памяти (память программ и память данных).
Информация, заложенная в энергонезависимую память сохраняется при выключении питания, и поэтому именно в нее записывается программа.
То место энергонезависимой памяти, куда записывается программа, называется памятью программ.
Объем памяти программ может быть различен. Для PIC16F84A он составляет 1024 слова.
Это означает, что он предназначен для работы с программами, объем которых не превышает 1024 слов (если не организуется внешняя память программ). Слово памяти программ не равно одному байту (8 бит), а больше его (14 бит).
Отдельная команда, которую ПИК будет в дальнейшем выполнять, занимает одно слово в памяти программ.
В зависимости от названия этой команды в ассемблере, слово принимает то или иное числовое значение в машинном коде.
После записи в ПИК программы, слова в памяти программ, в которые производится запись, "превращаются" в команды, которые располагаются в памяти программ в том же порядке, в котором они следуют в исходном тексте программы, написанном на языке ассемблер, и в том же порядке им присваиваются адреса, при обращении к которым та или иная команда извлекается из памяти программ для ее выполнения.
Последовательность же их выполнения определяется логикой программы.
Это означает, что выполнение команд может происходить не в порядке последовательного возрастания их адресов в памяти программ, а не последовательно ("скачком").
Дело в том, что только самые простые программы, в пределах одного полного цикла их работы, обходятся без этих "скачков", называемых переходами, и выполняются строго последовательно (на "линейном" участке).
Любая более-менее сложная программа разбивается на части, которые выполняют отдельные функции (своего рода программки в программе) и которые называются подпрограммами.
Безусловные переходы (переходы без условия) между подпрограммами (если они последовательно не переходят одна в другую), осуществляются командами переходов, в которых обязательно указывается адрес команды в памяти программ (косвенно - в виде названия подпрограммы или метки), на которую нужно перейти. Существуют также переходы с условием, то есть, с задействованием так называемого стека.
Более подробно о переходах я расскажу позднее.
Адреса команд определяются счетчиком команд (PC), то есть, каждому состоянию счетчика команд соответствует одна из команд программы.
Если команда простая, то счетчик просто инкрементируется (команды выполняются последовательно), а если команда сложная (например, команда перехода или возврата: об этом - позднее), то счетчик команд PC изменяет свое состояние "скачком", активируя соответствующую команду.
Примечание: инкремент - увеличение на единицу величины числа, с которым производится эта операция, а декремент - уменьшение на единицу (комплиментарные операции).
В простейшем случае, то есть в случае отсутствия в программе переходов, счетчик команд PC, начиная с команды START (нулевой адрес в счетчике команд), многократно инкрементируется, последовательно активизируя все команды.
Это означает, что за каждый (в большинстве случаев) машинный цикл (такт) работы ПИКа адрес увеличивается на 1. Таким образом последовательно перебираются все адреса (выполняются все команды программы от первой до последней).
В более сложных программах работу счетчика команд PC можно охарактеризовать фразой "Фигаро здесь, Фигаро там" (из за большого количества команд переходов).
Большинство команд выполняется за один машинный цикл, но есть и команды, выполняющиеся за два машинных цикла (например, команды ветвления типа: если X, то - так, а если Y, то - по другому).
Для ПИКов среднего семейства (слово = 14 бит), к которым относится PIC16F84A, один машинный цикл (такт работы программы) равен четырем периодам частоты опорного генератора (при применении кварца на 4 мГц, один машинный цикл = 1 мкс).
Совет: пока есть время, приобретите хотя бы один кварц на 4 мГц., в последующем он потребуется.
ВАЖНО: выполнение программы в рабочем режиме (кроме работы в режиме пониженного энергопотребления SLEEP) никогда не останавливается, то есть, каждый машинный цикл должно выполняться какое-либо действие (команда). Это объясняется тем, что опорный генератор, формирующий машинные циклы, работает постоянно.
Может сложиться ложное представление о том, что работу программы можно на какое-то время остановить, используя одну или несколько команд - "пустышек" (NOP) или любых других команд.
Это представление не верно, так как, в этом случае, речь идет только о задержке выполнения следующих рабочих команд, а не об остановке выполнения программы.
Программа выполняется и в этом случае, так как "пустышка" есть та же самая команда программы, только не производящая никаких действий.
Если нужно задержать, на относительно длительное время, выполнение каких-либо команд, то применяются специальные циклические подпрограммы задержки, о которых я расскажу позднее.
Даже тогда, когда программа "зависает" (то есть по каким-то причинам "крутится по вечному кольцу" внутри программы, не выходя на новый полный цикл программы и не реагируя на внешние воздействия), программа опять же выполняется, только не так, как нужно.
Остановить (в буквальном смысле этого слова) выполнение программы можно только прекратив работу опорного генератора.
Это происходит при переходе в режим пониженного энергопотребления (SLEEP), который используется в работе только достаточно специфических устройств типа пультов дистанционного управления (например, телевизором).
Отсюда следует вывод: программы, не использующие режим SLEEP (а таких - подавляющее большинство), для обеспечения непрерывного выполнения команд программы, должны быть либо циклическими (рабочая точка программы "наматывает кольца" в пределах полного цикла программы), либо иметь внутри себя хотя бы одну циклическую подпрограмму, в которой рабочая точка программы "закольцовывается" с возможностью "выхода" ее из этого "кольца" по внешнему воздействию.
Общий вывод
Команды программы записываются в память программ в порядке расположения команд в исходном тексте программы.
Адреса этих команд находятся в счетчике команд PC и каждому адресу соответствует одна из команд программы.
Команда активируется, если в счетчике команд находится ее адрес.
Активация команд происходит либо последовательно (на "линейном" участке программы), либо с переходом ("скачком") на другую команду (при выполнении команд переходов), с которой может начинаться либо подпрограмма (переход на исполнение подпрограммы), либо группа команд, выделенная меткой (переход на исполнение группы команд).
Выполнение команд программы никогда не останавливается (за исключением режима SLEEP), и поэтому либо сама программа должна быть циклической, либо она должна иметь внутри себя хотя бы одну циклическую подпрограмму, либо и то, и другое.
Если сечас это не понятно, то ничего страшного, позже будет "просветление".
Кроме памяти программ PIC16F84A имеет энергонезависимую память данных(EEPROM).
Она предназначена для хранения данных, которые необходимо сохранить после выключения питания в целях использования их при работе программы (при последующем включении/включениях питания).
Единицей ее объема является не слово, а байт (объем памяти данных - 64 байта).
Данные, хранящиеся в памяти данных, предназначены для их записи в стандартные 8-битные регистры, речь о которых пойдет далее.
Данные из этих регистров также могут быть записаны в память данных, то есть, может быть организован обмен данными между памятью данных и регистрами.
Например, именно память данных я использовал в своем частотомере для сохранения последних, перед выключением питания, настроек. Она же используется и для установки значения промежуточной частоты.
Во многих программах память данных вообще не используется, но это вещь исключительно полезная, и далее я расскажу о ней подробнее.
Регистры
 Включите свое образное мышление.
Вы купили механический конструктор. Что Вы делаете сначала? Изучаете его составные части для того чтобы прикинуть, куда что можно приспособить, что как можно соединить друг с другом, какие из нескольких отверстий нужно выбрать, чтобы соединить несколько деталей и т. д.
Если Вы хотите собрать из него нечто, Вы, используя результат полученных знаний, вырабатываете определенный план и реализуете его.
То же самое происходит и при работе с ПИКами.
"План" это программа, а "составные части конструктора" это и есть регистры.
Производя манипуляции с регистрами и встраивая их в программу (в замысел), можно реализовать огромное количество различных устройств.
Без детального знания всех элементов этого "ПИК - конструктора" не может быть и речи о какой-либо эффективной работе по составлению программ. Уделите этому особое внимание.
В ПИКах существуют две группы регистров: регистры общего назначения (GRP) и регистры специального назначения (SFR). По своей сути они являются элементами оперативной памяти и сохраняют информацию только при включенном питании.
Регистры общего назначения это "вотчина" программиста. Изначально они похожи на "пустые болванки" и толку от них - никакого.
Чтобы этот толк был, необходимо один или несколько из них (это зависит от замысла программы) "прописать" в программе (указать его название и адрес), приказать программе видеть регистр с этим названием и адресом и произвести замысленные программистом манипуляции с его содержимым, если команда обращается к нему (то же самое относится и к регистрам специального назначения).
Регистры общего назначения используются как однобайтная (или многобайтная, если для этой цели используются несколько регистров) оперативная память.
В основном, они используются для того, чтобы сохранить какие-либо числа с целью использования их в дальнейшем, причем, по ходу выполнения программы, они (числа) могут изменяться: увеличиваться, уменьшаться, сбрасываться, снова загружаться (как в неизменном, так и в измененном виде) и т.д..
Один и тот же регистр общего назначения (или группа регистров), при выполнении программы, может использоваться для работы с двумя или более функционально независимыми группами числовых данных. Например, в одной подпрограмме он может работать как счетчик, а в другой - как буферная память, но, обычно, этого не требуется, так как, в большинстве случаев, остаются свободные регистры, любой из которых можно задействовать для разгрузки уже задействованных регистров.
Когда, например, говорят, что на регистрах общего назначения X, Y, Z (их можно назвать как угодно, это дело вкуса программиста) собран 3-х разрядный вычитающий счетчик, следует иметь ввиду, что эти регистры, сами по себе, таковым счетчиком не являются, их делает этим счетчиком программа, а если конкретнее - программист.
Именно программа, вернее определенные ее команды и последовательность их выполнения, определяет порядок взаимодействия разрядов счетчика (старший, средний, младший), направление счета, точку начала и конца отсчета, наличие или отсутствие предварительной установки, момент сброса и т.д..
Чтобы говорить более предметно, скачайте этот файл: здесь.
На этой картинке Вы увидите область оперативной памяти для PIC16F84A.
Регистры общего назначения находятся в пустых клеточках. Каждой такой клеточке соответствует свой адрес.
Пустые (без названий) они потому, что названия им назначает программист.
Например, для работы в программе необходим регистр буферной памяти. Назовем его, например, Mem и присвоим ему адрес 0Ch (это делается в "шапке" программы, о чем - позже).
Это означает, что регистр Mem займет в области оперативной памяти ту клеточку, которая расположена правее желтой клеточки с надписью INTCON.
Все, регистр буферной памяти мы "прописали" (присвоили адрес в области оперативной памяти и назначили название), теперь с ним можно работать.
Эту картинку я делал для себя, и, кроме обучения, она очень полезна в работе.
Распечатайте ее в нескольких экземплярах, а, при составлении программы, в пустые клетки этой таблицы можно (и нужно, чтобы не запутаться) заносить, по определенным в программе адресам, названия регистров общего назначения и, при необходимости, какие-либо пояснительные надписи.
Это очень удобно и, лично я, всегда так делаю.
Вы без труда поймете, как определяется адрес той или иной пустой клетки (да и закрашенной тоже). В конце адреса всегда стоит буква h (признак 16-ричной системы исчисления). Именно в таком виде лучше всего указывать адрес регистра в "шапке" программы. Например, 0Ch, 14h, 29h, 2Ch... Можно использовать и прописные буквы: 0ch, 2dh, 1fh...
В работе над программой можно использовать 36 регистров общего назначения (см. картинку).
Область оперативной памяти разделена на 2 так называемых банка (в таблице их разделяет черная жирная линия).
Сверху расположен нулевой банк, в котором программы работают большую часть своего времени.
Что касается регистров общего назначения, то все они расположены в нулевом банке, а в первом банке просто дублируются (повторяются или, как часто говорят, отображаются).
Разделение области оперативной памяти на 2 банка обусловлено стремлением разработчиков микроконтроллеров создать как можно большее количество регистров общего назначения. Если поместить недублированные регистры специального назначения в один (нулевой) банк, то, при прочих равных условиях, количество регистров общего назначения уменьшится.
Регистры специального назначения
 Детали нашего ПИК-конструктора состоят не из одних регистров общего назначения. Это - самые простые детали.
По большому счету, к его деталям относится практически вся электронная начинка PIC контроллера: опорный генератор, система сброса, АЛУ, память, устройства, обеспечивающие прерывания, порты ввода - вывода, таймер TMR0, предделитель, сторожевой таймер WDT (перечислен минимальный набор для PIC16F84A, для более сложных ПИКов этот набор еще больше).
Перечисленные выше устройства имеют по нескольку режимов работы, конкретный режим работы которых выбирает программист.
Этот выбор режимов происходит в регистрах специального назначения и в регистре битов конфигурации.
Сначала рассмотрим регистры специального назначения (SFR).
Взгляните в область оперативной памяти. Эти регистры находятся в закрашенных желтым цветом клеточках (адреса 07h и 87h - пустые), и в них же находятся их названия.
Их названия и адреса фиксированы (неизменны) и манипулировать ими, в отличии от регистров общего назначения, просто нет смысла (можно, но зачем?).
А раз это так, то единственное, что можно с ними сделать, это изменить их содержимое, то есть произвести те или иные манипуляции с их битами (установить их в 0 или 1).
В зависимости от этих манипуляций, электронная начинка ПИКа приобретает необходимые для работы с программой свойства, что обеспечивает функционирование программы по замыслу программиста.
Регистры SFR с названиями, выделенными красным цветом, дублируются (отображаются) в обоих банках. Черным цветом выделены названия регистров SFR, которые не дублируются.
Работа с большей частью регистров SFR происходит в нулевом банке, при этом содержимое регистров SFR нулевого банка, выделенных красным цветом, дублируется (отображается) в банке №1.
Для работы с регистрами SFR первого банка, выделенных черным цветом, необходимо (при обращении хотя бы к одному из них) перейти в первый банк, произвести с их содержимым необходимые манипуляции, и вернуться после этого назад в нулевой банк. Манипуляций с их содержимым в нулевом банке производить нельзя.
Таких регистров 5 (см. область оперативной памяти) и их названия нужно особо запомнить именно по причине необходимости смены банка с нулевого на первый.
Во всех остальных случаях работа происходит в нулевом банке.
Кроме сказанного выше, некоторые регистры SFR, кроме рабочих битов, имеют еще и "нерабочие" биты, называемые флагами.
"Нерабочие" они в том смысле, что действий, непосредственно приводящих к каким-либо изменениям, эти биты не производят, но, обращаясь к их содержимому, можно считать информацию о результате какой-либо операции (например, является ли результатом этой операции ноль или нет), а затем использовать эту информацию при выборе одного из нескольких сценариев дальнейшей работы программы.
Таким образом, флаги могут опосредованно (косвенно) участвовать в рабочих действиях и вовсе не являются чем-то бесполезным.
Кроме всего прочего, при отладке программы, обратившись к соответствующим флагам, можно узнать много полезной и ценной информации, так что с ними нужно обязательно "дружить".
В свое время, я долго и нудно перерабатывал информацию из различных источников в поисках наиболее наглядного и понятного варианта распечатки состава регистров SFR, после чего плюнул на это дело и самостоятельно составил эту распечатку для себя.
Мне она сослужила хорошую службу, пусть послужит и другим.
Совет: сразу Вам будет сложно разобраться с тем, что Вы увидите, поэтому сделайте так, как сделал я: прикрепите эти листы к стенке на Вашем рабочем месте так, чтобы они висели перед Вами и постоянно бросались Вам в глаза.

Общая информация по SFR: здесь.
Регистр OPTION: здесь.
Регистр STATUS: здесь.
Регистр INTCON: здесь.
Регистр EECON1: здесь.
Биты конфигурации (о них - позднее): здесь.
Цоколевка PIC16F84A: здесь.
Все эти файлы относятся к PIC16F84A.
Обязательно скачайте еще один "продукт" моей деятельности: таблицу перевода чисел из одной системы исчисления в другую.
Это - одна из наиболее часто употребляемых таблиц и, при работе с программой, обращаться к ней Вы будете постоянно. Она находится: здесь.
В ней расписан "расклад" чисел в пределах одного байта. В большинстве случаев этого достаточно, но, если Вы будете работать с числами за пределами одного байта, используйте конвертор систем исчисления (см. выше).
Буквами D, B, H обозначены десятичная, бинарная, 16-ричная системы исчисления соответственно. Кроме того, в таблице указана стандартная (русифицированная) кодировка символов. Я этой таблицей пользуюсь постоянно.
Небольшое лирическое отступление для поднятия боевого духа: если, после восприятия изложенной выше информации, Вы начнете ощущать, что ожидаемое понимание предмета обучения не приходит, особо не расстраивайтесь.
На данной стадии это - нормально и естественно. В такого рода информации нужно немного "повариться", что предполагает приложение некоторых усилий.
"Кавалерийским наскоком" такие дела не делаются. Сейчас мы строим "скелет" и нужно отдельно разобраться с каждой "косточкой". Дело это конечно муторное, но совершенно необходимое, а "прозрение" придет позднее. Так что желаю Вам терпения и усидчивости.
Пошли дальше.
Разбираемся с битами регистров SFR.
Сразу расставлю приоритеты: в первую очередь, с целью недопущения "бардака" в головах, сначала я буду рассказывать о рабочих битах, а о флагах расскажу отдельно.
С этой же целью, на первых порах, я не буду вдаваться в некоторые необязательные, для начинающих, детали (о них - позднее).
Обращаю внимание: нумерация битов в байте происходит не слева - направо, а справа - налево. Это означает, что бит младшего разряда (бит с номером ноль) - крайний справа, а не слева. 7-бит - самый старший. Про нумерацию битов слева направо - забудьте).
В распечатках, красным цветом обозначены рабочие биты, зеленым цветом - флаги, серым цветом - "пустышки", про которые можно забыть (они не используются).
В регистре STATUS расположены три рабочих бита (5,6,7), из которых нас, пока, интересует только 5-й бит с названием RP0.
Именно этим битом переключаются банки (0 - нулевой банк, 1 - первый банк).
На момент старта программы, в ПИКе всегда автоматически выставляется нулевой банк. Это означает, что бит RP1 устанавливается равным нулю и для смены банка достаточно изменять только бит RP0.
Учитывая то, что 7-й бит также при этом устанавливается в 0 и в PIC16F84A 2-го и 3-го банка просто нет (они есть в более сложных ПИКах), то про биты 6 и 7 можно просто забыть (конечно, условно, до поры до времени) и, пока, не забивать себе ими голову.
Итак: в регистре STATUS, по факту, имеется всего один рабочий бит переключения банков RP0.
Регистр OPTION.
Все 8 битов - рабочие.
Биты PS0,PS1,PS2 определяют коэффициент деления предделителя (см. таблицу в распечатке).
Предделитель это последовательная цепочка из 8-ми триггеров, каждый из которых делит на 2.
Таким образом, максимальный коэффициент деления предделителя = 256 и он может задаваться значением битов PS0,PS1,PS2 в пределах разрешенных таблицей значений.
Предделитель может быть включен либо перед таймером TMR0, либо перед сторожевым таймером WDT. Это определяет 3-й бит регистра OPTION (PSA).
Таймер TMR0 - то же самое, что и предделитель (Кделения = 256), но с возможностью предустановки и синхронизации.
Он имеет 8 входов предустановки (предварительной установки), с которых, в процессе счета, в этот таймер может быть загружено 8-ми битное число.
Обычно, такого рода загрузку (числовую коррекцию) производят для задания некой начальной точки отсчета.
Если таймер TMR0 работает без предустановки, его коэффициент деления всегда равен 256 и не меняется. Единственным и достаточно специфическим случаем изменения Кделения TMR0 является случай работы с периодическими предустановками, который, из-за некоторых сложностей с синхронизацией, применяется достаточно редко.
В большинстве же случаев, перед TMR0 включается предделитель с заданным Кделения и, обычно, этого вполне достаточно для решения большинства задач.
Если предделитель включен перед TMR0, то, при поступлении команды сброса (уст. 0), они сбрасываются одновременно, причем такого рода сброс, по сути, является асинхронным, хотя, формально, он является как-бы синхронным, так как команда сброса "привязана" к машинному циклу (такту).
Если говорить человеческим языком, то команда сброса в 0 предделителя и таймера выполняется сразу и без задержек, связанных с процессом синхронизации, а сам этот процесс имеет место быть только в случае предустановки таймера TMR0. Сброс предделителя - всегда асинхронный.
Основная функция таймера TMR0 - подсчет количества импульсов за определенное программой время. Через каждые 256 импульсов (при переходе из состояния FF в состояние 00) происходит так называемое переполнение таймера (переход на новое кольцо счета), количество которых (при необходимости подсчета количества импульсов большего чем 256) подсчитывается.
Например: за 1сек. произошло 10 переполнений, значит TMR0 посчитал 2560 импульсов.
Если приплюсовать к этому количеству содержимое TMR0 на момент окончания счета, то мы получим точное количество импульсов, поступивших на вход TMR0 за 1сек.
На такого рода подсчете и основывается принцип работы, например, частотомера.
Если перед TMR0 включен предделитель, то в итог подсчета вносятся соответствующие коррективы, определяемые заданным коэффициентом деления предделителя.
Сторожевой таймер WDT это RC-одновибратор с перезапуском, формирующий импульс длительностью примерно 18мс.
При старте программы (если его работа разрешена), он запускается, и если его, в течении 18мс., не перезапустить, то он окончит формирование импульса и по его заднему фронту сформируется сигнал сброса программы на начало, то есть, программа возвратится к нулевому адресу и начнет исполняться снова.
А если полный цикл программы больше чем 18 мс.?
Зачем нужен такой "гемморой"? Оказывается, очень даже нужен.
В случае зависания, по какой-либо причине, программы, WDT может вывести ее из этого состояния, переведя программу в положение Start. Именно поэтому таймер WDT и назван сторожевым.
Для обеспечения этого сторожевого режима, необходимо, в ходе выполнения программы, периодически (через время не более 18мс.) сбрасывать WDT, то есть, не допускать его срабатывания.
Если перед WDT поставить предделитель (а такая возможность есть), то период сброса WDT можно увеличить (это зависит от заданного Кделения предделителя).
Так вот, 3-й бит (RSA) регистра OPTION и определяет, перед чем будет включен предделитель.
Если предделитель включен перед WDT, то они оба сбрасываются асинхронно по одной команде сброса WDT. Если WDT работает отдельно, то он также сбрасывается асинхронно.
4-м битом регистра OPTION (TOSE) устанавливается момент срабатывания таймера TMR0.
При установке его в 1, TMR0 срабатывает по спадам импульсной последовательности (переход от 1 к 0), присутствующей на его входе, а при установке его в 0 - по фронтам (переход от 0 к 1).
Значение 5-го бита регистра OPTION (TOCS) определяет, какой сигнал будет подаваться на вход TMR0: либо внешний сигнал с вывода RA4/TOCKI PIC16F84A (бит устанавливается в 1), либо внутренний тактовый сигнал CLKOUT (бит устанавливается в 0).
В качестве внутреннего тактового сигнала используется сигнал с частотой опорного генератора ПИКа, разделенной на 4, то есть, в этом случае, TMR0 будет считать каждый машинный цикл (1мкс. при применении кварца на 4мГц). Это - для случая работы TMR0 без предделителя.
При применении предделителя, TMR0 будет, естественно, считать медленнее (переполняться в течение более длительного интервала времени).
6-й бит регистра OPTION (INTEDG) определяет, по какому именно перепаду на входе внешнего прерывания INT будет начинаться выполнение подпрограммы прерывания (о прерываниях - позднее).
1 - "уход" в подпрограмму прерывания будет происходить по фронту сигнала на выводе RB0/INT PIC16F84A, 0 - по спаду.
В зависимости от состояния 7-го бита регистра OPTION (-RBPU), к выводам порта В либо подключаются, либо не подключаются подтягивающие резисторы (между выводами порта В и плюсом источника питания).
Они включаются или отключаются "оптом", то есть все 8, а не выборочно.
Обращаю внимание, что подтягивающие резисторы подключаются именно к выводам порта В, а не к входам или выходам порта В.
Выводы порта В могут работать как на вход, так и на выход.
При работе на выход, подтягивающие резисторы автоматически отключаются (если они были подключены), так как выходы защелок портов имеют свои подтягивающие резисторы, включенные постоянно (нагрузка защелок).
При работе на вход подтягивающие резисторы, если они были подключены, не отключаются.
Таким образом, подтягивающие резисторы, если они подключены, могут быть нагрузкой выходных каскадов устройств, подключенных к выводам порта В, работающим на вход, в случаях отсутствия нагрузки в этих каскадах.
Проще говоря, подтягивающие резисторы необходимо включать, если к выводу порта В, работающему на вход, например, подключаются выходы внешних устройств с открытым коллектором или открытым стоком.
В этом случае, подтягивающие резисторы являются нагрузками этих внешних устройств.
Если выходные каскады внешних устройств имеют свою нагрузку, то подтягивающие резисторы включать не нужно, иначе можно их перегрузить.
Регистр EECON1 управляет чтением - записью в EEPROM память данных.
Он имеет 3 рабочих бита, манипуляции со значениями которых происходит при стандартных процедурах чтения - записи.
По большому счету, забивать себе голову информацией об этих битах особо и не нужно.
Причина простая: при чтении и записи данных в EEPROM память данных, применяются рекомендуемые разработчиками стандартные процедуры (эти биты в них участвуют) и вносить в них какие-либо коррективы просто нет никакой необходимости. Творчество здесь совершенно неуместно и не нужно.
Если нужно произвести чтение или запись, то в программу просто вставляется соответствующая стандартная процедура, рекомендованная разработчиками, в которой эти биты уже установлены так, как нужно, так что можете их пропустить.
Об этих процедурах я расскажу позднее.
Сказанное выше относится и к регистру EECON2. Регистр INTCON имеет 5 рабочих битов. Это - регистр управления прерываниями.
Прерывание это приостановка выполнения основной программы с уходом в так называемую подпрограмму прерываний и возвратом из нее (после ее выполнения) в основную программу.
Выглядит это так: если в программе имеется подпрограмма прерываний, в шапке программы устанавливается так называемый вектор прерываний, который также, как и команда старта для основной программы, определяет начальный адрес счетчика команд для первой команды, но только не основной программы, а подпрограммы прерываний.
Программа стартует и выполняется пока не входит в зону команд, в которой прерывания разрешаются.
Если в этой зоне происходит, например, внешнее прерывание со входа RB0/INT ПИКа (приходит активный фронт), то адрес следующей команды основной программы запоминается (записывается в стек - об этом позднее), выполнение основной программы приостанавливается, и происходит переход по вектору прерывания на исполнение подпрограммы прерывания.
При этом происходит смена начальной точки отсчета счетчика команд PC и рабочая точка программы устанавливается на первую команду подпрограммы прерываний.
Далее подпрограмма прерываний выполняется по такому же принципу, как и основная программа, вплоть до ее окончания.
В конце подпрограммы прерываний всегда стоит специальная команда возврата, после исполнения которой, из стека извлекается адрес команды основной программы, перед которой произошел уход в прерывание и, начиная с этой команды, основная программа начинает исполняться далее.
Таким образом, прерывание - это своеобразный переход, в большинстве случаев, по внешнему воздействию, из основной программы на выполнение какой-то подпрограммы с возвратом.
В ПИКах несколько источников прерываний.
Для того, чтобы произошло любое из них, сначала нужно установить бит глобального разрешения прерываний GIE (установить в 7-м бите регистра INTCON единицу).
6-й бит (EEIE) разрешает/запрещает прерывания по окончании цикла записи в EEPROM память данных. Этот вид прерываний используется достаточно редко и на этот бит пока можно "закрыть глаза".
5-й бит (TOIE) разрешает/запрещает прерывания по переполнению TMR0, то есть, если этот вид прерываний разрешен, то уход в подпрограмму прерывания произойдет при смене содержимого TMR0 с FF на 00.
4-й бит (INTE) разрешает/запрещает внешнее прерывание по выводу RB0/INT ПИКа.
3-й бит (RBIE) разрешает/запрещает прерывания по изменению уровня сигнала на любом из входов RB7-RB4.
Примечание: направление работы выводов портов А и В можно устанавливать в любых комбинациях (см. регистры PORTA и PORTB. О них - ниже).
Те выводы портов А и В, на которые поступают сигналы прерываний, в зоне разрешения соответствующих видов прерываний, должны работать на вход.
Это относится к выводам RB0, RB4...7 порта В и выводу RA4 порта А.
Что касается последнего, то импульсную последовательность, подаваемую на счетный вход TMR0 (вывод RA4/TOCKI), вполне можно считать сигналом прерываний, только косвенно, так как прерывание наступает не по факту подсчета отдельного импульса, а по факту подсчета группы импульсов (переполнение TMR0).
Регистры TRISA и TRISB имеют 5 и 8 рабочих бита соответственно (по количеству выводов портов А и В). Их адреса - 85h и 86h (см. область оперативной памяти).
Распечатки на них нет, но они очень простые, и Вы без труда поймете, зачем они нужны.
Биты регистров TRISA и TRISB управляют направлением работы выводов портов А и В.
Если какой-нибудь из этих битов устанавливается в 1, то соответствующий вывод соответствующего порта работает на вход, то есть, принимает данные с выхода внешнего устройства, подключенного к этому выводу.
Если бит устанавливается в 0, то вывод порта уже сам является источником сигнала для подключенного к этому выводу входа внешнего устройства и программа может управлять этим внешним устройством (например, 7-сегментным индикатором или каким-то исполнительным устройством).
При помощи этих битов, в пределах каждого из портов, можно устанавливать различные комбинации направлений работы их выводов, а также и менять их (направления) в ходе выполнения программы.
Так как регистры TRISA и TRISB находятся в 1-м банке, то при работе с ними необходимо перейти в 1-й банк, произвести необходимые изменения битов и вернуться в нулевой банк.
Регистры PORTA и PORTB (см. распечатку: ниже регистра STATUS) управляют защелками портов А и В. Количество рабочих бит в них такое же, как и количество выводов портов (5 и 8).
Защелка это устройство оперативной памяти, а, проще говоря, триггер.
Так вот, биты регистров PORTA и PORTB управляют этими триггерами.
Если какой-либо из этих битов установить в 1, то на выходе защелки (на выводе порта, работающего на выход) также установится 1 (а для ноля - ноль).
Так как защелки являются триггерами, то перевод их выходов из одного состояния в другое происходит одномоментно.
Это означает, что, например, для вывода цифры на 7-сегментный индикатор в течение определенного программой интервала времени, достаточно одной команды на вывод цифры в начале этого интервала времени и одной команды на ее сброс в конце интервала.
В промежутке времени между этими двумя командами, программа может выполнять какие-либо другие действия, формируя за счет этих действий данный интервал времени, причем эти действия могут быть напрямую не связанными с работой по выводу цифры в 7-сегментный индикатор (например, может производиться подсчет количества импульсов, операции с регистрами, работа с EEPROM - памятью и т.д.).
Следует четко уяснить следующее: биты регистров PORTA и PORTB защелками управляют всегда, но не всегда данные с выходов защелок присутствуют на выводах портов.
Выходы защелок портов подключаются к выводам портов только при работе этих выводов на выход.
При работе выводов портов на вход, выходы защелок от соответствующих выводов портов отключаются.
В отличие от выводов порта В, к которым могут быть либо подключены, либо отключены подтягивающие резисторы, выводы порта А подтягивающих резисторов не имеют.
Таким образом, если они настроены на работу на вход, то к ним нужно подключать внешние подтягивающие резисторы.
Если нужно организовать счет импульсов (задействован TMR0), то роль подтягивающего резистора для вывода RA4/TOCKI может выполнять, например, коллекторная нагрузка транзисторного формирователя импульсов (посмотрите как это сделано в принципиальной схеме ЧМ/ЦШ А.Денисова).
Регистр счетчика команд PC (13 бит) разделен на 2 регистра. В регистре счетчика команд PC с названием PCL находится младшие 8 бит адреса выбираемой для исполнения команды.
Во время работы программы, содержимое регистра PCL постоянно меняется.
Напрямую менять содержимое регистра PCL, обычно, нет необходимости, так как существуют более простые способы переходов с одной команды на другую, но косвенное воздействие на его содержимое, называемое вычисляемым переходом, применяется довольно-таки часто.
Вычисляемым переходом называется приращение (только в сторону увеличения порядковых номеров адресов) содержимого регистра PCL, установившегося на момент выполнения данной команды, на величину содержимого задаваемой программистом константы (или на величину содержимого специального регистра, называемого аккумулятором W: о нем - позднее).
Проще говоря, если перед выполнением вычисляемого перехода в регистре PCL находилась команда, например, с адресом 5 , а программист задал величину константы =2, то, в результате выполнения команды вычисляемого перехода, произойдет переход на команду с адресом 7.
Таким образом, вычисляемый переход является четвертой разновидностью переходов по программе после переходов по стеку, переходов на подпрограммы и переходов на метки.
Какой именно из этих переходов применить - решает программист в соответствии с оптимальным, на его взгляд, способом решения стоящих перед ним задач.
Регистр PCL это только младшая 8-ми битная часть регистра счетчика команд PC, и, по этой причине, с содержимым регистра PCL, при переходах, можно производить манипуляции только в пределах 256-ти адресов (команд).
Обращаю внимание: в этом случае речь идет не об отсчете этих 256 адресов от начала их списка, а об отсчете адресов от адреса команды, с которой осуществляется переход, до адреса команды, на которую осуществляется переход.
Это означает, что, например, значение константы (или содержимого регистра W), в случае вычисляемого перехода, не должно превышать этого значения, иначе произойдет переход не по требуемому адресу.
К этому же нужно стремиться и вообще при любом виде перехода, чтобы не нажить себе "гемморой" при работе с регистром PCLATH.
Поясняю: кроме 8-ми младших битов счетчика команд PC, находящихся в регистре PCL, есть еще старшие 5 бит, находящиеся в регистре PCH (он даже и не показан в области оперативной памяти). Сюрприз заключается в том, что он напрямую не доступен для чтения и записи, и эти операции происходят через дополнительный регистр PCLATH, в котором, для PIC16F84A, рабочими являются только 2 его бита (с младшей стороны).
Изменяя их значения перед переходом, можно переходить на другие группы команд.
Чтобы не наживать себе подобного рода проблемы, лучше составить программу так, чтобы при любом из ее переходов происходил скачек на команду, отстоящую от команды начала скачка не более чем на 256 команд. В большинстве случаев это удается без проблем. Если это так, то о регистре PCLATH можно вообще забыть.
Если это соблюдается, то по ходу выполнения программы, при пересечении границ 256-й, 512-й и т.д. команды, содержимое регистра PCLATH автоматически инкрементируется и программисту не нужно вносить в его содержимое каких-либо поправок и вообще иметь с ним дело.
Регистры INDF и FSR - регистры косвенной адресации.
Здесь необходимо кое-что пояснить.
Существует 2 вида адресации: прямая и косвенная.
Прямая адресация это обращение к регистру напрямую, то есть в команде указывается название регистра, с содержимым которого нужно произвести какие-то действия.
При этом программа автоматически определяет адрес этого регистра в области оперативной памяти.
При косвенной адресации название регистра не указывается, а указывается адрес этого регистра в области оперативной памяти, предварительно заложенный в регистр FSR.
Для того, чтобы осуществить действие с регистром, адрес которого заложен в FSR, необходимо обратиться к регистру INDF.
Для начинающих это звучит достаточно непонятно. Советую Вам информацию по косвенной адресации просто принять к сведению и, пока, особенно на ней не зацикливаться.
Конечный результат прямой и косвенной адресации один и тот же, просто в некоторых случаях (например, при работе с табличными данными), используя косвенную адресацию, можно обойтись меньшим количеством команд.
Сначала нужно как следует освоить прямую, как более простую для понимания, а только после этого переходить к косвенной.
И то, и другое нам еще предстоит сделать. И, наконец, самый часто используемый регистр - регистр W или его еще называют аккумулятором. Если по простому, то это тот же самый регистр общего назначения, только с определенным названием (в том смысле, что оно заранее определено так же, как и названия регистров специального назначения).
Его адрес, в отличие от регистра общего назначения, знать не обязательно и прописывать его адрес не нужно. Стоит только обратиться к нему, и адрес автоматически поставится ему в соответствие.
Он является 8-битным и выполняет функцию буферной памяти обычно на время одного, двух машинных циклов (а также и более).
Наиболее часто он применяется по той простой причине, что большое количество команд ориентировано именно на операции с этим регистром (буква W входит в состав этих команд).
Например, чтобы перегрузить данные из одного регистра в другой, сначала данные из регистра-отправителя загружаются в регистр W, а затем из W загружаются в регистр-получатель.

Флаги
Определение флагов дано выше.
Основная часть флагов находится в регистре STATUS.
Самый часто применяемый из них - флаг нулевого результата (2-й бит с названием Z). Он показывает, является ли результатом проведенной операции нулевой результат (флаг поднят, что соответствует установке 2-го бита регистра STATUS в 1) или не нулевой результат (флаг опущен: во 2-м бите - 0).
Кроме информирования программиста о результате операции, содержимое бита Z может являться критерием выбора того или иного сценария работы программы, для чего он чаще всего и применяется.
Например, программист поставил условие: если результат операции не равен 0, то программа исполняется дальше, а если равен нулю, то происходит переход на выполнение подпрограммы (или на метку) ABCD.
После выполнения операции, флаг или останется опущенным (0) или поднимется (1).
Далее следует команда ветвления типа: если 2-й бит регистра STATUS не равен 0, то программа исполняется дальше, а если равен 0, то происходит переход на выполнение подпрограммы (или на метку) ABCD.
Дешево и сердито.
Если использовать цепочку таких проверок, то можно, например, выяснить: попадает ли результат операции в какой-то числовой сектор или нет, равен ли он какому-то числу или нет, и, в зависимости от этого, направить программу в то или иное "русло".
Не трудно заметить, что анализ содержимого бита Z (а также и некоторых других флагов), происходит при выполнении так называемых команд ветвления типа: если X, то так, а если Y, то по другому, применяющихся при определении сценариев работы программы.
Необходимо также отметить, что, при использовании одной команды ветвления, возможных сценариев может быть только 2, а при применении нескольких - количество возможных сценариев будет кратным двум.
Флаги переноса - заема С (нулевой бит регистра STATUS) и десятичного переноса - заема DC (1-й бит) показывают, было ли переполнение байта или младшего полубайта байта (извиняюсь за тавтологию) соответственно.
Переполнение это выход результата операции за пределы максимально возможного числа, определяемого разрядностью. Например, наибольшее число в пределах байта (8 разрядов) - 255.
Если, например, складываются числа 200 и 100, то происходит переполнение, и флаг C устанавливается в 1 (поднимается).
Кроме того, при выполнении команд сдвига (вспомните работу сдвиговых регистров), флаг C загружается старшим или младшим битом сдвигаемого регистра (в зависимости от направления сдвига).
На флаги переполнения сторожевого таймера и включения питания внимания, пока, можете не обращать. Они используются достаточно редко и, в большинстве случаев, чисто информативны.
То же самое относится и к флагам EEIF и WRERR регистра EECON1 (см. распечатку).
Флаги TOIF, INTF, RBIF регистра INTCON работают по единому принципу, отличия только в источниках прерываний.
Перед работой с ними, их необходимо сбросить программно.
С содержимым регистров специального назначения мы, в основном, разобрались. Пошли дальше.
Метки
Само название уже отражает смысл этого понятия.
Если нужно осуществить переход на выполнение команды программы, отстоящей от команды начала перехода на какое-то количество команд, и не являющейся первой командой подпрограммы, эта команда помечается меткой.
Метка представляет собой результат фантазии программиста (латинские буквы и цифры), которая ставится в специально отведенное для нее (них) место в тексте программы на одной строке с командой, на которую нужно осуществить переход (так же, как и название подпрограммы).
Четкой границы между метками и названиями подпрограмм нет и различия между ними установлены как бы для наведения порядка в программе.
Формально, название подпрограммы указывает на первую ее команду, а метка помечает команду, отличную от первой команды подпрограммы - вот и все различие.
Сама же техническая сторона переходов по меткам и переходов на подпрограммы - одна и та же.
Самое главное - понимать смысл и технику такого рода переходов, а проклассифицировать их можно на свое усмотрение.
Аппаратный стек
Поддерживает команды переходов по условию (условные переходы) на выполнение подпрограмм (переходы на подпрограммы) и групп команд (переходы на метки).
В отличии от команды перехода без условия (безусловного перехода), после выполнения которой не предусмотрен автоматический возврат программы к месту, от которого был осуществлен переход (например, после выполнения подпрограммы, на которую был осуществлен переход), команды переходов по условию обеспечивают такой возврат.
Чтобы его обеспечить и нужен стек.
В упрощенном виде он представляет собой некую таблицу данных из одной колонки и восьми строк.
При выполнении команды условного перехода, в верхнюю строку записывается 13-ти разрядное слово (PCH 5 бит + PCL 8 бит), определяющее адрес следующей, после команды условного перехода, команды (для дальнейшего возврата программы по этому адресу).
Если до возврата из первого условного перехода поступает еще одна команда условного перехода, то адрес возврата из первого условного перехода сдвигается вниз по таблице на одну позицию, занимая вторую строку, а адрес возврата из второго условного перехода помещается в первую строку таблицы, называемой вершиной стека.
Таким образом, стек можно заполнить 8-ю адресами возвратов, после чего наступает его переполнение (указателя переполнения стека нет), чего допускать нельзя, то есть, в стек может быть заложено не более 8-ми адресов возврата.
Даже в достаточно сложных программах работа стека на грани переполнения - редкое явление и его емкости вполне достаточно.
Это объясняется тем, что, по мере работы программы, адреса возвратов из стека извлекаются, освобождая свободное место.
Адреса возвратов извлекаются только из вершины стека, а остальные 7 его строк используются как оперативная память для остальных адресов возврата, которые либо сдвигаются к вершине стека по ходу извлечения из него адресов возвратов, последовательно занимая вершину стека и "уходя" из него, либо наоборот, сдвигаются вниз от вершины при выполнении новых команд условных переходов.
И то, и другое может чередоваться, что предполагает определенную последовательность как закладки адресов возвратов в стек, так и и их извлечения из него.
Спешу успокоить начинающих. Для того, чтобы прочувствовать что такое программирование, достаточно работы с одной вершиной стека, то есть с первой его строкой.
В нее можно последовательно заложить и извлечь хоть десяток адресов возвратов, не забивая тем самым себе голову проблемами с остальными строками.
Естественно, в этом случае, нужно позаботиться, чтобы следующий условный переход осуществлялся только после возврата из предыдущего условного перехода.
Если Вы это освоите, то, со временем, Вы естественным образом перейдете к более сложным манипуляциям с условными переходами.
Это произойдет тогда, когда Вы захотите сделать свою программу более компактной.
Некоторые программы вообще не содержат условных переходов (стек не задействован), а в некоторых случаях без них никак не обойтись (например: обработка прерываний).
Итак: микроконтроллер это набор многофункциональных устройств. Программа определяет конкретные функции этих устройств и их режимы работы на различных участках выполнения программы (см. регистры специального назначения).
На регистрах общего назначения строятся различные устройства, применяемые в цифровой технике. Таковыми устройствами их делает программа.
И в том, и в другом случае именно программа определяет все то, что будет происходить с этими элементами ПИК-конструктора, и поэтому далее речь пойдет о начинке программы, то есть о командах.
Переходите к следующему разделу.

Переход к оглавлению


Kea_23

Переход к оглавлению

3. Система команд PIC16F84A

В свое время я составил для себя достаточно подробное описание этих команд и предлагаю его Вашему вниманию.

Файл с этим описанием Вы можете скачать: здесь.

Команды разделены на 3 группы:
- байт-ориентированные команды, то есть команды, производящие действия с байтами,
- бит-ориентированные команды, то есть команды, производящие действия с битами,
- команды управления, то есть команды, осуществляющие переходы и возвраты, а также команды сброса сторожевого таймера WDT и перехода в спящий режим SLEEP.
Из списка команд управления можно смело вычеркнуть последние 2 команды, так как они могут быть использованы только для поддержки совместимости PIC16F84A с более "древним" семейством микроконтроллеров PIC16C5x, а для совместимости PIC16F84A с последующими выпускаемыми микроконтроллерами, эти команды разработчиками вообще не рекомендуется использовать.
Итого: осталось 35 команд. Давайте с ними разбираться.
Сначала - информация по таблице команд.
В первой колонке располагается название команды, а в рамке под этим названием - краткое содержание команды.
Сразу обращаю внимание, что подчеркивание команды есть только элемент ее выделения, и ничего более. В тексте программ команды не подчеркиваются.
Латинские буквы в конце команды обозначают:
f - название регистра (в общем виде), к которому эта команда обращается, и с содержимым которого будет производиться действие.
Иными словами, наличие в команде буквы f означает, что команда обращается к содержимому регистра, с которым программист хочет произвести определяемое этой командой действие.
d - указатель места сохранения результата при операциях с байтами. Если d = 0, то результат исполнения команды сохраняется в регистре W (в аккумуляторе).
Если d = 1, то результат исполнения команды сохраняется в регистре f, то есть в регистре, с содержимым которого производилось действие.
b - номер бита в регистре, с которым производится действие.
k - константа, то есть некое число, которое задается программистом.
Во второй колонке находится более подробное описание действия, которое производит команда.
В третьей колонке даны примеры выполнения команд.
В четвертой колонке указано количество машинных циклов, в течении которых данная команда выполняется. Большая часть команд выполняется за 1 машинный цикл.
Для команд, выполняющихся за 2 машинных цикла, двойка находится в скобках.
Комбинация числа 1 и двойки в скобках означает, что по одному сценарию выполнения команды, она выполняется за один машинный цикл, а по другому - за два.
Напоминаю, что подобного рода команды (команды ветвления) имеют только 2 сценария ее исполнения.
В пятой колонке указаны флаги, которые реагируют на выполнение данной команды.
Пятая колонка команд, на выполнение которых флаги не реагируют, закрашена серым цветом.
Сразу обратите внимание на следующую особенность, знание ее очень поможет Вам при запоминании команд: значительная часть команд состоит из 5-ти букв, причем корень слова команды, характеризующий ее суть, состоит, в большинстве случаев, из 3-х букв, а последние две буквы показывают, откуда и куда копируется (переписывается) число или с какими регистрами нужно произвести определяемое командой действие.
Например, команду ADDWF можно условно разделить на 2 части: корень ADD, который обозначает операцию сложения, и WF, где первая буква обозначает регистр W (аккумулятор), а вторая - регистр F, то есть регистр, к которому обращается команда.
Следовательно, из самого названия команды становится понятным, что складывается содержимое регистров W и F.
В большинстве случаев, в качестве этого условного регистра F, используются регистры общего назначения, а реже - регистры специального назначения.
Например, если в шапке программы мы определили название какого-нибудь регистра общего назначения как ABCD, по ходу программы заложили в него какое-нибудь число и решили сложить это число с числом, находящимся в аккумуляторе (W), с сохранением результата сложения в том же регистре ABCD, то это будет выглядеть так:
addwf ABCD,1
При этом следует отметить, что в регистр W программист может переписать содержимое любого другого регистра, и, по сути своей, такого рода операция есть операция сложения чисел, находящихся в двух регистрах с выбором места сохранения ее результата.
Рассмотрим команду ADDLW
ADD - операция сложения, L - признак того, что в ней участвует константа, W - признак того, что в ней участвует регистр - аккумулятор. Например:
addlw k
Это означает, что складывается содержимое регистра W и некая константа, заданная программистом. Если она равна, например, числу 3, то это будет выглядеть так:
addlw 3
Так как константа не является регистром, то место сохранения результата операции однозначно - регистр W.
Другой пример: операция копирования содержимого одного регистра в другой.
movwf ABCD
Расшифровка: MOV - обозначение команды копирования. Копирование данных производится из регистра W в регистр F (то есть, в регистр ABCD).
Так как никакой логической операции при этом не совершается, то и вопроса о сохранении результата неосуществленной логической операции не встает, просто происходит банальное копирование данных.
Обращаю внимание, что в системе команд для ПИКов, при простом перемещении данных из одного регистра в другой (то есть при операции без логических действий), речь идет именно о копировании без вырезания данных из регистра - отправителя (и вообще без какой - либо их трансформации).
Конечным результатом действия этой операции является числовое равенство содержимого обеих регистров.
Аналогично:
AND - операция побитного "И" содержимого двух регистров,
CLR - сброс в ноль содержимого регистра,
COM - инверсия всех битов регистра,
DEC - декремент содержимого регистра (минус 1),
INC - инкремент содержимого регистра (плюс 1),
IOR - побитное "ИЛИ" содержимого двух регистров,
NOP - пустой машинный цикл (ничего не происходит),
RLF - циклический сдвиг содержимого регистра влево,
RRF - циклический сдвиг содержимого регистра вправо,
SUB - операция вычитания содержимого одного регистра из содержимого другого,
XOR - побитное "исключающее ИЛИ" содержимого двух регистров,
BTF - команды ветвления по значению выбранного в регистре бита,
RET - возврат с загрузкой константы в W,
4 буквы в корне команды:
SWAP - поменять местами полубайты регистра,
CALL - переход по условию,
GOTO - переход без условия,
5 букв в корне команды:
SLEEP - уход в режим пониженного энергопотребления,
6 букв в корне команды:
RETURN - возврат из подпрограммы (кроме подпрограмм обработки прерываний),
RETFIE - возврат из подпрограммы обработки прерываний.
1 буква в корне команды:
B - операции с битом. Имеются ввиду команды BCF и BSF.
Вторая буква в этих командах является признаком операций с битом: C - установить бит в 0, S - установить бит в 1. F - признак регистра.
Получается: сбросить (0) или установить (1) какой-то из битов регистра, к которому обращается команда.
С учетом сказанного выше, и того, что таблица команд содержит достаточно подробную информацию, я остановлюсь на наиболее трудно понимаемых командах.
На данном этапе обучения от Вас требуется только общее понимание смысла действий, производимых командой и заучивание их названий.
В дебри лезть незачем, иначе навредите сами себе. "Хозяином" этих команд Вы станете позднее - тогда, когда пойдет работа по составлению программ, а пока вживайтесь в команды ассемблера.
Наибольшие трудности, на первых порах, обычно, вызывают команды ветвления.
Сразу следует уяснить, что ветвления, то есть выбор какого-то одного из двух сценариев работы программы, могут быть результатом выполнения различных действий.
Команды ветвления:
Байт - ориентированные: DECFSZ, INCFSZ.
В подавляющем своем большинстве эти команды обращаются к регистрам общего назначения.
Самый распространенный случай: внутри полного цикла программы имеется еще один внутренний цикл, и рабочая точка программы должна "прокрутиться" по этому внутреннему циклу, например, 10 раз, чтобы после этого выйти на полный цикл программы.
В качестве счетчика такого рода проходов назначается какой-нибудь из регистров общего назначения. Назовем его, например, XYZ.
В начале полного цикла программы в него закладывается число проходов, то есть, число 10.
Программа пошла дальше, вышла на первый внутренний цикл, а в его конце стоит команда DECFSZ, в результате действия которой содержимое регистра XYZ уменьшается на единицу и становится равным 9-ти. Эта девятка сохраняется в том же регистре XYZ.
Так как это число не равно 0, происходит переход на начало внутреннего цикла программы и все повторяется снова до тех пор, пока, после очередного вычитания единицы, в регистре XYZ не установится число 0.
Как только это произойдет, будет осуществлен переход не на начало внутреннего цикла программы, как это было ранее (первый сценарий работы программы), а на начало полного цикла программы, который, обычно, помечается словом START (второй сценарий работы программы).
Из этого примера видно, что происходит то, что называют ветвлением программы.
Ветвление может происходить и в пределах маленькой подпрограммы.
Еще один пример.
К порту В подключен 7-сегментный индикатор. Полный цикл программы равен, например, 1 сек., а индикатор высвечивается в течении 0,1 сек., а остальное время погашен. Требуется увеличить соотношение: индикатор высвечивается - индикатор погашен.
Следовательно, необходимо "встроить" в тот участок программы, в котором индикатор активен, подпрограмму задержки.
Такие подпрограммы имеют в своей основе все те же команды DECFSZ (INCFSZ), только переходы при этом гораздо "скромнее" - всего на несколько команд, а не на десятки, то есть программа, на время задержки, крутится по очень маленькому кольцу подпрограммы задержки.
Выглядит это так.
Подпрограмма задержки, в простейшем случае, начинается с команды DECFSZ (INCFSZ) и обращается к регистру общего назначения, в который, перед ее началом (в промежутке времени между моментом активации индикатора и до начала подпрограммы задержки), закладывается некое число (константа), которое и будет определять время задержки.
Предположим, что в регистр XYZ мы заложили число 250.
Стоит только рабочей точке программы войти в подпрограмму задержки, она не выйдет из этой подпрограммы, пока подпрограмма полностью не исполнится.
Смысл этой "закольцовки" заключается в том, что при, выполнении, например, команды DECFSZ и отсутствии нулевого результата операции декремента, переход происходит на все ту же команду DECFSZ и все повторяется по новой, пока число 250 не уменьшится до нуля, а это требует времени (вот Вам и задержка).
При нулевом результате операции, рабочая точка программы выходит из подпрограммы задержки, после чего индикатор переводится в пассивное состояние и программа исполняется далее.
Зная длительность машинного цикла, можно точно посчитать время задержки.
Этот пример, конечно, приведен в упрощенном виде, так как для больших задержек, при длительности машинного цикла = 1мкс, одного регистра не достаточно (необходимо несколько регистров с последовательным их декрементом).
В этом мы разберемся позднее. Сейчас главное - прочувствовать "кольцевую природу" программ и представить себе, как в главном кольце программы образуются большие и маленькие кольца и как программа в них входит, выходит, "идет по прямой", заходит в другое кольцо и т.д.
Чтобы составить об этом визуальное представление, перейдите на главную страницу моего сайта, щелкните там по ссылке "Дополнительная информация по частотомеру А.Денисова" и, после перехода по этой ссылке, посмотрите файл блок- схемы программы.
Особо не ужасайтесь, "не так страшен черт, как его малюют", а просто прикиньте, с чем Вы будете иметь дело.
Примечание: все, сказанное выше, относится и к команде INCFSZ. Отличие - только в направлении счета (+1).
Оставшиеся две команды ветвления BTFSC и BTFSS ориентированы на операции с выбираемыми программистом битами регистров.
При применении этих команд, никаких логических (арифметических) операций с байтами не происходит, а критерием ветвления является значение бита.
Например, если на момент выполнения команды BTFSC значение выбираемого программистом бита регистра XYZ (например, бита 2) равно 1, то выполняется следующая команда, а если значение этого бита равно 0, то вместо следующей команды автоматически вставляется "пустышка" в виде команды NOP (нет действия или операции), и после этого пустого машинного цикла выполняется следующая команда (вторая после BTFSC).
Обращаю внимание, что эта команда NOP (в этом случае) как бы виртуальна, и в тексте программы она не прописана, хотя, по факту, она и заменяет, в случае равенства 2-го бита нулю, следующую после BTFSC команду, которая в тексте программы прописана.
Смысл всей этой "свистопляски" в том, что, в случае равенства 2-го бита нулю, выполнение следующей рабочей команды произойдет только на втором машинном цикле после команды BTFSC, а не на следующем (первом) машинном цикле после команды BTFSC, как в случае равенства 2-го бита единице.
Вот Вам и объяснение того, что Вы видите в 4-м столбце таблицы команд, в строке с командой BTFSC.
Если кому так проще, то он может просто представить себе, что рабочая точка программы, в случае равенства 2-го бита нулю, как бы "перескочила" через следующую после BTFSC команду, "не заметив" ее (за 2 машинных цикла).
То же самое, с учетом специфики команд, относится и к остальным командам ветвления (DECFSZ, INCFSZ, BTFSS).
Может быть несколько вариантов "развития событий":
пример 1-го и самого распространенного варианта
btfsc XYZ,2
goto Cycle
----- -----
Если 2-й бит регистра XYZ равен 1, то происходит безусловный переход в подпрограмму, например, Cycle.
Если 2-й бит регистра XYZ равен 0, то перехода в подпрограмму Cycle не происходит (вместо этого - виртуальный NOP) и программа выполняется дальше (дальнейшие команды программы обозначены ---- ----).
пример 2-го варианта
btfsc XYZ,2
goto Cycle
goto Mem
Если 2-й бит регистра XYZ равен 1, то происходит безусловный переход в подпрограмму Cycle.
Если 2-й бит регистра XYZ равен 0, то происходит безусловный переход в подпрограмму Mem. Команда goto Cycle игнорируется (вместо нее исолняется команда виртуального NOPа).
пример 3-го варианта
btfsc XYZ,2
movwf PortB
----- -----
Если 2-й бит регистра XYZ равен 1, то содержимое аккумулятора (W) копируется в защелки порта В. После этого копирования программа исполняется дальше.
Если 2-й бит регистра XYZ равен 0, то копирования содержимого аккумулятора в защелки порта В не происходит (вместо этого - виртуальный NOP), и программа исполняется дальше.
В первых двух примерах используются переходы, в последнем - не используются.
Во всех случаях происходит ветвление, так как имеется два сценария работы программы.
Все, что сказано выше, относится и к команде BTFSS. Единственное различие в том, что критерий ветвления меняется на противоположный.
Для начинающих, в первую очередь, необходимо обратить внимание на команду безусловного перехода без возврата GOTO.
Сначала нужно освоить ее, а потом переходить к изучению команды условного перехода CALL.
В принципе, при составлении программ можно вообще не применять команду CALL, а пользоваться только командой GOTO как по своему прямому назначению, так и заменяя ей команду CALL.
Обе эти команды переход осуществляют одинаково, только в случае безусловного перехода возврата нет, а в случае условного (задействован стек) - есть.
Принцип замены команды CALL на GOTO состоит в том, что, кроме команды GOTO, по которой происходит переход, необходима еще одна команда GOTO, которая организует возврат.
Обычно возврат происходит на команду, не являющуюся первой командой подпрограммы (то есть она ничем не помечена, а при использовании команды GOTO необходимо указать либо название подпрограммы, располагающееся на одной строке с первой командой подпрограммы, либо метку, располагающуюся на одной строке с помеченной командой), поэтому команду, на которую осуществляется возврат, необходимо пометить меткой, а в команде возврата указать эту метку.
Это выглядит примерно так:
----- -----
goto Cycle
Metka xxxx xxxx
----- -----
----- -----
Cycle yyyy yyyy
----- -----
goto Metka
----- -----
Объяснение:
----- ----- обозначены какие-нибудь команды программы. Их может быть разное количество (зависит от программы).
По команде безусловного перехода GOTO сначала осуществляется переход на подпрограмму Cycle.
Следующая после GOTO команда, обозначенная иксами, предварительно помечается меткой Metka для обеспечения возврата.
Происходит переход на подпрограмму Cycle и она, начиная с первой ее команды, обозначенной игреками, выполняется до своей последней команды, в качестве которой используется все та же команда GOTO, но, на этот раз, выполняющая функцию возврата.
Происходит переход (возврат) на команду, выделенную меткой Metka и программа исполняется дальше.
Между Metka и Cycle может быть, например, 10, 50, 100 или другое количество команд, в которых также могут быть организованы переходы и возвраты.
Таким образом можно организовать переходы и возвраты без использования команды CALL, то есть без работы со стеком. Результат - один и тот же.
Рекомендую начинающим, на первых порах, поступать именно так, а со стеком поработаете когда переходы и возвраты перестанут вызывать у Вас затруднения.
Для любознательных: описанная выше процедура для случая условного перехода выглядит так.
Первую сверху команду GOTO нужно заменить на команду CALL. Метка Metka не нужна. Нижняя команда GOTO заменяется на специальную (для работы со стеком) команду возврата RETURN (без указания точки возврата: ее адрес хранился в стеке).
Команда RETLW также работает со стеком. От команды RETURN она отличается только тем, что при возврате осуществляется действие: в регистр W загружается константа. Команда RETURN никаких действий, кроме возврата по стеку, не производит.
Команда RETFIE применяется только для возврата из подпрограммы прерываний, о которых будет отдельный разговор.
Команда CLRWDT используется для периодического сброса сторожевого таймера WDT, если он включен.
В отличии от команды MOVWF, в которой все ясно и понятно, команда MOVF может вызвать у начинающих некоторые затруднения.
Команда MOVF копирует содержимое указанного регистра либо в регистр W (аккумулятор), либо в тот же указанный регистр.
Так как последняя операция, казалось бы, лишена практического смысла, то, в основном, эта команда работает с регистром W.
Таким образом, команда MOVF копирует в регистр W содержимое указанного в команде регистра.
Но, копирование содержимого указанного в команде регистра в этот же регистр, все-таки имеет практический смысл: можно проверить, равно или нет содержимое регистра нулю.
Если содержимое регистра равно 0, то флаг нулевого результата Z поднимется (установится в 1), а если нет - опустится (установится в 0).
Манипуляции с флагом Z будут рассмотрены позднее.
Чаще всего эта команда применяется для считывания данных с выводов портов.
Выглядит это так: movf PortA,W или movf PortB,W
После того, как в аккумулятор скопируются биты состояния выводов того или иного порта, эти данные можно скопировать из регистра W в какой-нибудь другой регистр с целью дальнейшей их обработки, например, для выяснения состояния клавиатуры, подключенной к выводам порта.
Обращаю Ваше внимание, что напрямую скопировать данные из одного регистра в другой (например из одного регистра общего назначения в другой регистр общего назначения) нельзя, их (данные) нужно сначала скопировать из регистра - адресата в буферную память (регистр W), а затем перекопировать данные из нее в регистр - получатель.
Для случая копирования данных из регистра PortB в регистр общего назначения XYZ это выглядит так:
movf PortB,W
movwf XYZ
Буквально: скопировать содержимое регистра PortB в аккумулятор, а затем скопировать содержимое аккумулятора в регистр XYZ. Или проще: переслать содержимое регистра PortB в регистр XYZ.
Примечание: в приведенных выше и ниже примерах использования команд я не придерживаюсь правил, используемых при написании программ, так как в HTML-редакторе, которым я пользуюсь при оформлении этой информации для отображения в Интернете, довольно-таки хлопотно их придерживаться.
Синтаксические правила написания программ будут изложены позднее, этим правилам Вы будете обучаться в специализированном редакторе программы MPLAB, и мы будем работать с файлами с расширением .ASM.
Стандартные логические операции с корнями AND ("И"), IOR ("ИЛИ"), XOR ("исключающее ИЛИ"), COM (инверсия) в особых пояснениях не нуждаются.
При выполнении команд RLF и RRF, происходит циклический сдвиг влево и вправо, соответственно, содержимого регистра, к которому обращается команда.
Сдвиг происходит через флаг переноса - заема (нулевой бит регистра STATUS).
Эти команды используются при переводах чисел из одной системы исчисления в другую, например, из двоичной в двоично - десятичную.
При такого рода переводах используется также команда SWAPF.
Начинающим советую пока, на время, "отложить последние 3 команды в сторону" и особо на них не "зацикливаться", "разборки" с ними будут позднее.
Команда NOP (нет операции) на первый взгляд может показаться не слишком значимой, но это не так.
При составлении программ для ПИКов, достаточно часто встречаются ситуации, когда необходимо осуществить задержку выполнения какой-то последующей команды, не производя при этом никаких операций с числами, или ситуации, когда необходимо уравнять по времени оба сценария команды ветвления, не производя при этом никаких операций, и т.д.
В этих случаях и используется команда NOP.
Если NOPы вставляются в "линейный" участок программы, то каждый NOP задерживает выполнение следующей активной команды на 1 машинный цикл, а если NOPы вставляются в циклические подпрограммы, то, в зависимости от их конструкции, время исполнения программы вцелом может увеличиться на десятки, сотни и даже тысячи машинных циклов.
Переходите к следующему разделу.

Переход к оглавлению


Kea_24

Переход к оглавлению

4. Что такое программа и правила ее составления. Пример создания программы автоколебательного мультивибратора. Директивы.

Говоря о программе, я, прежде всего, буду иметь ввиду текст программы.
Программа это некая последовательность команд, которая реализует замысленные программистом действия с числами.
Производя те или иные действия с заранее заданными числами и/или с данными, поступающими от внешних устройств , можно сформировать требуемые алгоритмы управления внешними исполнительными устройствами.
Специфики работы по созданию устройств на дискретных элементах цифровой техники и работы по созданию устройств на микроконтроллерах значительно отличаются друг от друга.
При создании устройств на дискретных элементах, разработчик имеет дело только с машинными кодами, что весьма хлопотно и трудоемко даже для профессионалов.
Устройства получаются громоздкими и габаритными, на их изготовление тратится много сил и времени.
При создании устройств на ПИКах, разработчик имеет дело не с машинными кодами, а с универсальным языком программирования ассемблер, при помощи команд которого задачи решаются гораздо проще и эффективнее.
Устройства, по схемотехнике, получаются очень простыми, компактными и дешевыми.
Задача желающего научиться программированию ПИКов, на первых порах, сводится к изучению его "начинки", изучению команд ассемблера и принципов построения нескольких фундаментальных подпрограмм.
Потом, когда Вы "войдете во вкус", все станет гораздо проще и интереснее (по себе знаю).
Итак, мы выяснили, что любая программа носит циклический характер и ее рабочая точка может двигаться по командам программы либо последовательно, если нет команд переходов, либо совершить "скачек" в то место программы, которое указывается в команде перехода (с возвратом и без возврата), либо "закольцеваться" на какое-то время в той или иной подпрограмме.
При этом могут образовываться большие и малые кольца. Самым большим кольцом является полный цикл (кольцо) программы, который при работе программы исполняется снова и снова, до тех пор, пока Вы не выключите питание.
Естественно, что, прежде чем писать программу, необходимо четко определиться с принципом построения и принципиальной схемой создаваемого устройства.
Пример:
 Необходимо разработать простую программу, реализующую функцию автоколебательного мультивибратора с одним выходом (второй, то есть инверсный, выход не нужен). Форма сигнала - меандр.
Назначаем в качестве этого выхода, например, вывод RB0 порта В (можно назначить любой из выводов порта А или В). Следовательно, необходимо установить режим работы вывода RB0 - на выход.
Исходя из принципа работы автоколебательного мультивибратора, программа должна быть циклической и один полный ее цикл должен последовательно включать в себя группу команд формирования низкого уровня напряжения (0) и группу команд формирования высокого уровня напряжения (1) на выводе RB0.
Форма сигнала "меандр" предполагает одинаковое время формирования этих уровней.
Предположим, что это время должно быть равным 100мкс для каждого из уровней.
Выстраиваем упрощенный "скелет" программы:
После команды Start, переводим вывод RB0 на работу "на выход".
Далее формируем на выводе RB0 нулевой уровень в течении 100мкс.
Далее формируем на выводе RB0 единичный уровень в течении 100мкс.
Переходим на новый цикл, то есть на команду Start и так до "бесконечности" (пока питание не будет выключено).
Как составляется эта программа:

Файл текста программы Вы найдете: здесь.

Перед рабочим текстом программы (рабочими командами) расположена "шапка" программы, в которой указывается тип ПИКа (PIC16F84A) и значения битов конфигурации (что переключают эти биты - посмотрите в распечатке).
Устанавливаем эти биты: например, бит защиты CP выключен, сторожевой таймер WDT выключен, кварцевый генератор (используем кварц на 4мГц.) работает в режиме стандартного генератора.
И тип ПИКа, и установленные биты конфигурации (а также "прописка" в программе регистров специального и общего назначения, разрешение на начало выполнения программы и ориентир конца программы - см. ниже) вводятся в работу программы так называемыми директивами (не путать с командами программы).
О них будет рассказано ниже, а также и в других разделах "Самоучителя...".
Далее в "шапке" программы "прописываются" адреса регистров специального назначения (см. область оперативной памяти).
Так как нам нужно только определить направление работы вывода порта В и далее работать только с этим выводом, необходимо "прописать" адреса регистров TRISB (выбор направления работы выводов порта В), PORTB (работа с защелками порта В) и STATUS (необходимо переключение банков, так как регистр TRISB находится в 1-м банке).
Остальные регистры специального назначения "прописывать" не нужно, так как команды программы обращаться к ним не будут.
Таким образом, в "шапке" программы "прописываются" те регистры специального назначения, к которым обращаются команды программы.
Далее в "шапке" программы "прописываются" адреса регистров общего назначения.
Нужно с ними определиться.
В предыдущем разделе шла речь о задержках. Это как раз тот случай. Нам необходима задержка 100мкс.
Для обеспечения такой задержки достаточно емкости одного регистра общего назначения.
Этот регистр будет участвовать и в процессе формирования нулевого уровня, и в процессе формирования единичного уровня.
Назовем его, например, Sec и, директивой equ, определим ему адрес, например, 0Ch в области оперативной памяти. Все, регистр "прописан".
В конце "шапки" располагается директива разрешения выполнения программы и команда перехода на начало программы (обычно - Start).
Для более сложных программ "шапка", конечно, более объемна и содержит большее количество "прописанных" регистров и директив (в том числе и других типов).
Ничего особо сложного в оформлении "шапки" нет, самое главное - знать, что "прописывать", то есть, заранее определиться с регистрами, которые будут задействованы по ходу выполнения программы.
Итак, приступаем к составлению программы:
После "срабатывания" команды goto Start (она находится в конце "шапки" программы - см. текст программы), происходит переход на первую команду рабочей части программы (нулевой адрес в счетчике команд PC).
Она помечена словом Start, которое можно считать либо названием подпрограммы, либо меткой - кому как нравится. Обычно, его считают названием подпрограммы.
Далее работаем в соответствии с выстроенным "скелетом" программы.
Так как регистр TrisB располагается в первом банке, то переходим в первый банк.
Для этого нужно установить в единицу 5-й бит регистра Status. Делаем это (bsf Status,5).
Так как направления работы выводов порта В RB1-RB7 нас не интересуют (они не задействованы), проще всего установить все биты регистра TrisB в ноль, то есть, записать в этот регистр константу 0.
Так как напрямую записать константу в регистр нельзя, "транспортировка" этого нуля в регистр TrisB производится через аккумулятор (регистр W), что Вы и видите в программе (movlw .0 и movwf TrisB). В этом случае задействуются байт-ориентированные команды.
Другой способ: задействуем бит- ориентированную команду BCF.
В этом случае, в ноль устанавливаются не все биты регистра TrisB, а только бит с номером ноль: bcf TrisB,0 (сброс нулевого бита регистра TrisB в 0).
На практике чаще применяется первый вариант, так как, в этом случае, можно минимальным количеством команд выставить любой вариант направлений работы выводов портов для случаев, когда задействуется количество выводов портов большее чем 1. На нем и остановимся.
Все, дело сделано: вывод RB0 теперь работает на выход, то есть, к нему подключился выход защелки.
Так как дальнейшие манипуляции будут производиться в нулевом банке, то возвращаемся в нулевой банк (устанавливаем 5-й бит регистра Status в 0: bcf Status,5).
Все перечисленные выше операции можно считать подготовительными и, в большинстве случаев, подпрограмма Start начинается именно с таких операций.
Для того, чтобы осуществить декремент содержимого регистра Sec (вспомните про задержки), необходимо сначала определить это содержимое, а затем заложить его в регистр Sec.
Предположим, что мы определились с содержимым (десятичное число 32. Почему именно оно - далее).
Число .32 (точка - атрибут десятичной системы исчисления) является константой (то, что задается программистом) и поэтому напрямую записать его в регистр Sec нельзя (см. выше).
Проделываем эту операцию через регистр W (movlw .32 и movwf Sec).
Число .32 в регистр Sec записано и в дальнейшем содержимое этого регистра можно декрементировать.
Итак, все готово для управления защелкой RB0 порта В.
Формируем нулевой уровень на выводе RB0.
Применяем бит - ориентированную команду BCF и указываем в ней нулевой бит регистра PortB, который нужно сбросить в 0 (bcf PortB,0).
Все, дело сделано и на выводе RB0 ноль зафиксируется до тех пор, пока мы его не снимем (вспомните, что защелка является триггером).
Теперь наша задача сделать так, чтобы с момента установки на выводе RB0 нуля и до момента смены этого нуля на единицу, прошло ровно 100мкс.
Установка на выводе RB0 единицы происходит так же, как и установка нуля, только применяется команда BSF (см. текст программы).
Таким же образом, как описано выше, происходит и закладка константы в регистр Sec (для формирования времени установки на выводе RB0 единицы).
Давайте разберемся, почему константа для формирования времени установки на выводе RB0 нуля должна быть именно .32.
Применяется кварц на 4мГц., следовательно, один машинный цикл равен 1мкс.
Таким образом, с момента установки на выводе RB0 нуля и до смены его на единицу должно пройти 100 машинных циклов.
Подпрограмма задержки Pause_1 - циклическая.
При неравенстве результата декремента содержимого регистра Sec нулю, один "виток" этой подпрограммы занимает 3 машинных цикла: команда DECFSZ выполняется за один м.ц., а команда GOTO - за два.
Таким образом, уменьшение на 1 содержимого регистра Sec происходит за 3 м.ц. (3мкс.).
При равенстве результата декремента содержимого регистра Sec нулю, команда GOTO не выполняется (вспомните: вместо нее - "виртуальный" NOP), и выход из подпрограммы Pause_1 происходит за 2 м.ц.
Следовательно, константе 32 соответствует 32*3-1=95 м.ц. (95мкс.).
Плюсуем к 95-ти 2 м.ц. двух команд NOP, 2 м.ц. команд установки константы в регистр Sec для формирования времени единичного уровня на выводе RB0 (movlw .30 и movwf Sec) и 1 м.ц. команды установки на выводе RB0 единицы (bsf PortB,0) и получаем ровно 100мкс.
Теперь о двух NOPах, которые стоят перед ПП Pause_1.
Если сделать константу равной 33-м, то получится неустранимый перебор (рабочие команды из программы не выкинешь).
Если константа равна 32-м, то получается недобор в 2мкс., который устраняется добавлением двух NOPов.
После того, как результат декремента содержимого регистра Sec станет равным нулю, программа исполняется дальше, по образу и подобию ее исполнения для случая формирования на выводе RB0 нулевого уровня.
Все то же самое, только на выводе RB0 устанавливается единица.
Естественно, что, в этом случае, в регистр Sec необходимо заложить новую константу, так как, после декремента при формировании времени нулевого уровня, он обнулен.
Различие числовых значений констант при формировании нулевого и единичного уровней обьясняется тем, что при формировании на выводе RB0 единицы, исполняется команда GOTO (2 м.ц.) и команды ПП Start (в конце текста программы осуществляется безусловный переход на ПП Start: goto Start).
Кто имеет такое желание - посчитайте, это полезно.
Если кого-то пугают такого рода расчеты, то хочу Вас успокоить: в программе MPLAB, с которой мы будем работать далее, существует счетчик машинных циклов и при отладке циклических подпрограмм, с его помощью, подобные этой операции, существенно упрощаются и становятся не такими "страшными" как может показаться на первый взгляд.
Разбираемся с директивами ассемблера.
Директивы это такие же команды, как и команды рабочей части программы, но только не включаемые в выходной код.
Выходной код - результат ассемблирования текста программы, а, проще говоря, содержимое HEX-файла, то есть файла, предназначенного для открытия в программе, обслуживающей программатор.
Еще проще: команды рабочей части программы включаются в HEX-файл, а директивы - нет.
Тогда зачем они нужны?
Дело в том, что директивы за пределы MPASM (программа - ассемблер, которая является составной частью MPLAB) не выходят и исполняются только внутри нее.
Директивы можно уподобить администратору - распорядителю какого-то мероприятия.
Их задача - расставить все по своим местам перед началом "мероприятия" (рабочей части программы) для того, чтобы оно прошло успешно и объявить о его окончании (директива end).
Не смотря на большое количество всевозможных директив, которое может даже устрашить, на практике применяется весьма ограниченное их количество и практически все они относятся к так называемым директивам данных.
Файл с описанием самых распространенных директив, а также синтаксис числовых значений в различных системах исчисления (об этом - позже) Вы можете скачать
здесь.
 Сразу же заучите директивы EQU, __CONFIG, ORG, END. Они используются практически во всех программах. Есть они и в нашей программе.
Найдите их в тексте программы и, с помощью таблицы директив, Вы без труда поймете что они "администрируют" (распределяют и назначают).
Как видите, ничего особо сложного в этих процедурах нет.
Примечание: в тексте программы обратите внимание на то, что регистры TrisB и PortB "прописаны" директивой EQU по одинаковым адресам (06h).
С нулевым банком все понятно: регистр PortB имеет в области оперативной памяти адрес 06h, а вот регистр TrisB (первый банк) вместо адреса 86h прописан все по тому же адресу 06h. В чем дело?
А дело в том, что после перехода в первый банк, по факту, нумерация адресов регистров специального назначения в нем такая же, как и в нулевом банке.
Адреса с восьмеркой это просто "заморочка" разработчиков. Если прописать регистр TrisB по адресу 86h, то при ассемблировании (создании HEX-файла) программа-ассемблер выдает предупреждение о том, что с банками не все в порядке.
Даже если с HEX-файлом при этом все в порядке, я все-таки рекомендую Вам при прописке адресов регистров специального назначения первого банка, обозначенных в области оперативной памяти черным цветом, назначать им адреса по принципу "минус 80" и руководствоваться этим правилом всегда.
Синтаксические правила написания программы
 Программа - ассемблер замучает Вас сообщениями об ошибках, если не выполнены определенные правила написания текста программы.
Давайте в них разберемся на примере написанной нами программы.
Все что находится правее точки с запятой (;) программа - ассемблер "не видит", то есть после ; программист может настукать все что угодно по своему усмотрению и на любом языке. Дальнейшие комментарии, на мой взгляд, излишни - все просто как мычание коровы.
А вот дальше - сложнее.
Активная часть и шапки программы, и рабочей части программы разделена на 3 столбца.
Обращаю Ваше внимание (см. текст программы), что левые буквы содержимого первого (левого) столбца должны занимать крайние левые позиции строк, на которых они находятся. Это - как бы начальная точка отсчета.
Второй и третий столбцы находятся правее, и первые символы их содержимого должны располагаться на одной вертикальной линии.
Последние символы содержимого столбца не должны "наезжать" на первые символы соседнего справа столбца. Между ними должен быть хотя бы один пробел.
Чтобы не забивать Вам голову рассуждениями о возможных компановках расстояний между столбцами, рекомендую Вам пользоваться следующим правилом: от крайней левой позиции строки (начало 1-го столбца) отсчитываете 12 пробелов. Это будет начало 2-го столбца.
Затем отсчитываете 12 пробелов от начала 2-го столбца. Это будет начало 3-го столбца.
От начала 3-го столбца отсчитываете еще 12 пробелов. Это будет начало комментариев.
Такой вариант "проверен жизнью", я пользуюсь им всегда и Вам советую.
Содержимое столбцов
Начну с "шапки" программы.
В 1-м столбце (левом) располагаются названия всевозможных регистров, а также названия битов регистров специального назначения (в программе Multi их нет, но в других, более сложных программах, они есть).
Если для регистров определяется их адрес, то для битов регистров специального назначения определяется их номер (в пределах соответствующего байта).
После этого, в рабочей части программы, при обращении команд к этим битам, можно вместо номера бита указать его название.
Во 2-м столбце находятся директивы.
В 3-м столбце находятся адреса, присваиваемые регистрам, и номера битов, соответствующие тому или иному названию бита в регистрах специального назначения.
В конце "шапки" располагается команда GOTO START, а при наличии в программе подпрограммы прерываний - директива ORG с указанием адреса вектора прерываний (об этом - позже).
Переходим к рабочей части программы.
В 1-м столбце располагаются названия подпрограмм и меток .
Во 2-м столбце располагаются названия команд.
В 3-м столбце располагаются названия регистров, к которым обращаются команды, в том числе и с указанием битов этих регистров (для бит - ориентированных команд) и места сохранения результата операции.
Кроме того, в 3-м столбце указываются значения констант.
В конце программы обязательно должна присутствовать директива END.
Если убрать все то, что находится правее точек с запятой (оформление, комментарии), то это и будет текст программы в "чистом виде", то есть то, с чем работает MPLAB при ассемблировании (создании HEX - файла).
Теперь - о форме представления чисел.
Числа можно отображать в рабочей части программы в трех видах: 16-ричном, десятичном и бинарном (таблица перевода и соответствующая программка у Вас уже имеются).
Чтобы MPLAB поняла, в какой системе исчисления представлено число, необходимо его соответствующим образом оформить.
Как это делается, Вы можете увидеть в таблице синтаксиса числовых значений, которую Вы найдете снизу от таблицы директив MPASM.
В особых комментариях эта таблица не нуждается, Вы без труда в этом разберетесь.
Обращаю Ваше внимание: для того, чтобы не создавать на первых порах путаницы, связанной с переводом чисел из одной системы исчисления в другую, в дальнейшем, я буду, в основном, пользоваться привычной, то есть - десятичной системой исчисления.
В программе Multi константы представлены именно в десятичной системе исчисления (с точкой слева).
Для общего развития: до девятки включительно 16-ричная и 10-тичная системы исчисления одинаковы.
Байт, то есть 8 бит укладывается в этот диапазон, поэтому, при нумерации битов в байте, все равно, в какой системе исчисления происходит эта нумерация: в 16 или 10-чной.
Практический вывод из этого очень простой: номер бита можно просто указать цифрой, не забивая себе голову, в какой системе исчисления представлено это число.
Если Вы просто указали это число без каких бы то ни было атрибутов, то формально, по умолчанию, оно будет представлено в 16-ричном виде, но нам-то какая разница, ведь в 10-чном виде оно точно такое же.
А вот после девятки этот "номер" уже не пройдет, так как появляются расхождения.
Бинарная система исчисления стоит особняком и подобным правилам не подчиняется.
В "шапке" программы все адреса регистров должны указываются в 16-ричном виде, а все остальное - либо в 16-ричном, либо в 10-чном виде (при нумерации битов, просто пишете номер бита - см. выше).
Числа в бинарной системе исчисления в "шапке" программы, на практике, просто не применяются.
Итак, правила оформления текста программы, на самом деле, не являются чем-то страшно трудным. Скорее всего наоборот, они достаточно просты и освоить их не составляет особого труда.
Краткий итог: мы сейчас уже вплотную подошли к практической работе с текстами программ, что предполагает "ввод в действие тяжелой артиллерии" в виде комплекса программ MPLAB.
Переходите к следующему разделу.

Переход к оглавлению


Kea_25

Переход к оглавлению

5. Интегрированная среда проектирования MPLAB IDE и работа в ней.

MPLAB это набор программ, которые позволяют программисту с максимальным комфортом составлять, отлаживать и оптимизировать текст программы, а также создавать (после этого) HEX - файл программы.
Она также имеет и другие "навороты" (в самом уважительном смысле этого слова), которыми начинающим, для их же пользы, не стоит пока забивать себе голову.
Давайте сначала разберемся с "прожиточным минимумом".
Предполагается, что MPLAB уже установлен на жестком диске Вашего компьютера и готов к работе.
Если Вы этого не сделали, щелкните по разделу "Готовим инструменты".
Поехали...
Общие положения.
 MPLAB создана конкретно для ПИКов. Для микроконтроллеров других видов имеются свои среды проектирования.
MPLAB поддерживает все типы ПИКов, за исключением самых новейших разработок (их можно догрузить) и существенно облегчает работу программиста.
Ее можно представить себе как друга, который берет на себя значительную часть рутинной и трудоемкой работы, который укажет на шибку, если она совершена, и который поможет Вам в решении проблемных задач, которых, при создании и отработке программ, Вы встретите великое множество.
Редко когда бывает так, что составление программы идет "как по маслу". Скорее всего такое - исключение из правил (особенно для достаточно сложных программ) и поэтому "прожиточный минимум" MPLAB нужно знать как "Отче наше".
Сразу же нужно уяснить следующее: работа по созданию текста своей программы или работа по изменению текста чужой программы всегда начинается с создания так называемого проекта.
Что-то изменять в тексте программы, производить ее оптимизацию и отладку лучше всего в проекте.
В этом случае, с максимальным комфортом, можно пользоваться полным набором возможностей MPLAB.
Многое можно сделать и вне проекта, но, в этом случае, возникают определенные неудобства, которые нам ни к чему.
В целях определения ясных и понятных "правил игры", давайте договоримся о следующем: составлять, оптимизировать, отлаживать тексты программ и подпрограмм и вообще вносить в их тексты какие-либо изменения мы будем только в проекте.
Вне проекта мы будем только просматривать тексты программ, при необходимости, ассемблировать их и просматривать HEX - файлы.
Вне проекта мы также будем открывать файлы, предназначенные для копирования в проект, и это понятно, так как в пределах проекта осуществляется работа только со "своими" файлами и для того, чтобы изменить их содержимое необходимо либо сделать это "вручную", либо, в самом распространенном случае, скопировать в них через буфер обмена содержимое какого-то "внешнего" файла.
Вне проекта никаких изменений в текстах программ мы производить не будем.
Советую Вам и в дальнейшем работать по этим правилам, так как это, на мой взгляд, самый оптимальный вариант.
Программист может в проекте создавать текст программы или/и вносить в него изменения, а также вносить изменения в текст чужой программы (.ASM), но практической необходимости прямого изменения содержимого HEX -файла нет (хотя это и можно сделать).
При ассемблировании текста программы всегда создается HEX - файл программы на момент ассемблирования, и этот процесс, в ходе работы над текстом программы, можно проводить любое количество раз, не боясь того, что при этом "наплодится" большое количество HEX - файлов, в которых можно запутаться.
Объяснение этому простое: для любого из текстов программ, "право на жизнь" имеет только один HEX - файл, и, при очередном ассемблировании текста программы, вместо старого HEX - файла записывается новый.
Что там при этом происходит в дебрях MPLAB программисту и не нужно знать, никто лучше MPLAB с этой задачей не справится.
Скажу больше. Начинающим (и не только) вообще не нужно забивать себе голову содержимым HEX - файла. Такого рода разборки - удел квалифицированных хакеров, а для всех остальных MPLAB (вернее программа - ассемблер MPASM, входящая в состав MPLAB) сделает все в лучшем виде.
Самое главное - было бы что ассемблировать, и чтобы это "что" было без ошибок (в противном случае, выдается сообщение об ошибках с указанием вида ошибки и места ошибки в тексте программы).
Если текст программы (любой) открыт в MPLAB и нужно создать HEX - файл программы, то программисту нужно только запустить MPASM и дождаться окончания ее работы.
Единственное что нужно сделать в случае успешного ассемблирования - определить место хранения HEX - файла на жестком диске Вашего компьютера (если Вы работаете вне проекта) или вообще ничего не делать (если Вы работаете в проекте, то HEX - файл "уходит" в папку проекта - об этом позднее).
Кроме HEX - файла, при ассемблировании, MPLAB создает еще несколько файлов с различными расширениями, которые, на данном этапе обучения, являются "архитектурными излишествами" и, пока, нас не интересуют.
Лично я, после того как заканчиваю работу над программой, либо вообще удаляю их из папки проекта, чтобы они не занимали места, либо просто не обращаю на них внимания.
Основные файлы папки проекта, без которых обойтись нельзя:
- файл проекта с расширением .PJT: чисто "технологический" файл, который "рулит" всеми остальными файлами папки (вспомните про "администратора"). Создается при создании проекта. К нему можно применить принцип: "создал и забыл" . Он "живет" по законам MPLAB и нам там делать нечего,
- файл текста программы с расширением .ASM,
- результат ассемблирования текста программы - файл с расширением .HEX
Сразу после создания проекта, в папке проекта имеется PJT - файл и "пустой" ASM - файл. Так как ассемблирования не производилось (да и что ассемблировать, ведь ASM - файл "пустой"), то HEX - файл в папке пректа будет отсутствовать, и он появится там после первого же ассемблирования.
В "пустом" ASM - файле можно либо "настучать" программу с "нуля", либо скопировать в него предварительно заготовленные части будующей программы и далее ее доработать, либо скопировать в него текст чужой программы с целью изменения и/или дополнения этого текста и т.д.
Все манипуляции с содержимым ASM - файла происходят в специальном текстовом редакторе MPLAB.
По принципу своей работы, он мало чем отличается от обычных текстовых редакторов и работать с ним даже гораздо проще, чем, например, с Вордовским редактором.
Специальным он называется по той причине, что встроен в MPLAB. Что это дает?
При возникновении ошибок в тексте программы, прямо в редакторе будет указана строка с ошибкой.
В тексте программы может быть организован поиск чего-то повторяющегося, например, команд, которые обращаются к тому или иному регистру.
В ходе отладки программы, Вы будете видеть исполняемую команду, а также назначать точки остановки для проверки работы составных частей программы и т.д.
По всей видимости, для Вас еще не совсем понятны эти нюансы, и я говорю о них только лишь для того, чтобы создать общее представление о том, с чем придется иметь дело.
Ничего страшного, на практике все гораздо проще, чем на словах. Скоро Вы убедитесь в этом сами.
Итак, текстовый редактор это "посредник" в "общении" программиста и программы в ходе ее составления, оптимизации и отладки, поэтому основную часть времени работы с программой Вы будете проводить именно в нем.
За этим немудреным (с точки зрения манипуляций с текстом) редактором, тем не менее, стоит вся "мощь" MPLAB и, в этом отношении, любой, даже самый "крутой" редактор, "работающий сам по себе" и применяемый при программировании, ему и "в подметки не годится".
Зауважали? Правильно сделали. Спецы "Микрочипа" свою зарплату отрабатывают по полной программе.
Совместно с текстовым редактором работает так называемая программа - симулятор.
Из самого названия понятно, что она что-то "симулирует", то есть, "создает видимость работы".
Она является средством отладки программы и позволяет, без применения реального ПИКа, симитировать работу программы в своего рода "виртуальном" ПИКе.
Этот "виртуальный" ПИК, на самом деле, физически не существует, и он создается программными средствами MPLAB, причем, конкретно для каждого типа ПИКа.
Симулятор, конечно, не "всесилен", но с его помощью (плюс некоторые хитрости) можно проверить работу программы практически во всех возможных режимах (как вцелом, так и по частям), отстроить ее временные характеристики, найти ошибки и проверить их устранение.
Симулятор это та "палочка - выручалочка", которая применяется в случае "корявой" работы программы, зависания или в случае, когда программа не заработала или работает не так, как нужно.
Программа - симулятор, покажет Вам, по каким адресам находятся те или иные регистры и какие изменения происходят с их содержимым, посчитает количество машинных циклов на выбранном Вами участке программы, поможет симитировать ту или иную ситуацию и т.д., и все это - без загрузки программы в реальный ПИК, что экономит кучу времени и нервов.
По большому счету, программисту вовсе не обязательно знать, какой именно программой из набора программ MPLAB производятся те или иные действия.
Достаточно только знать, какое именно действие нужно произвести, как его запустить и как проверить результат этого действия, но, все-таки лучше, если программист, хотя бы в общих чертах, имеет представление о структуре "инструмента", с которым он работает, хуже от этого не будет.
Итак, ближе к практике.
Работа с MPLAB
Проще всего для восприятия не создавать программу с "нуля", а создать проект какой-нибудь готовой программы с целью редактирования ее текста.
В качестве этой программы я буду использовать, приведенную выше в качестве примера, программу Multi.asm
На ее примере я покажу Вам на практике как производится ассемблирование, как редактируется текст программы и как происходит отладка программы.
Настройка MPLAB
 Мудрить с настройкой не нужно. По умолчанию, разработчики MPLAB установили оптимальный вариант настройки, за что им можно сказать спасибо.
Что нужно сделать: проконтролировать (на всякий случай) включение симулятора и выставить тот тип ПИКа, для которого будет составляться программа (в нашем случае это PIC16F84A), определить стандартный режим работы генератора (XT), значение частоты кварца (4мГц) и единицу измерения частоты (мГц).
Запустите MPLAB. После этого Вы увидите "фасад" программы с пустым окном программы.
Управление - привычное: сверху - главное меню, под ним - кнопки, дублирующие основные команды.
Самая левая кнопка - выбор различных раскладок этих кнопок.
Таких раскладок - 4 (на все случаи жизни), но, лично я, пользуюсь только одной. Если в этой раскладке не хватает какой-то нужной мне команды, то я использую команды главного меню, что и Вам советую.
Пощелкайте по самой левой кнопке: найдите и оставьте ту раскладку, в которой кнопки с двумя светофорами занимают 9-ю и 10-ю позиции слева.
В главном меню щелкните по слову Options, а затем - по строке Development Mode.
Откроется окно Development Mode с активной вкладкой Tools. Если вкладка Tools не активна, то щелкните по ней.
На вкладке Tools должна быть поставлена точка перед MPLAB SIM Simulator (симулятор включен).
Если это не так, то поставьте ее и не снимайте, так как симулятор нужен всегда.
Щелкните по кнопке списка Processor. Вы увидите список ПИКов, c которыми MPLAB работает.
Нужно выбрать из этого списка PIC16F84A.
Перейдите на вкладку Clock и поставьте там точку перед MHz. Укажите значение частоты применяемого кварца (4.000000). В выпадающем списке Oscillator Type нужно выбрать XT (стандартный генератор).
Щелкните по OK.
Все, настройка MPLAB закончена. Остальные настройки - по умолчанию (то есть, делать больше ничего не нужно).
Создаем проект
 Если для файлов проекта заранее не определено место их хранения, то все они будут свалены в кучу внутри программы MPLAB и Вы будете долго и нудно их искать среди большого количества файлов программы (причем, в разных местах).
В целях недопущения такого "бардака", необходимо заранее (до создания проекта) определить место, где эти файлы будут компактно храниться.
Советую Вам делать так, как делаю я:
Не запуская MPLAB, зайдите в папку программы (например, через проводник).
Для ориентира: Вы должны увидеть папку Example. Здесь же создайте папку с названием, например, Pic, а внутри папки Pic создайте пустую папку Multi (в нее мы и поместим все файлы проекта Multi).
Таким образом, в папке Pic будут храниться папки с файлами различных проектов, которые мы будем создавать. Дешево, сердито и никакого "бардака".
Открываем MPLAB.
Сначала необходимо создать пустой файл, который, впоследствии, мы назовем, присвоим расширение (.ASM) и сохраним в папке Multi.
Это делается очень просто: щелкнуть по слову File в главном меню, а затем по New в выпадающем списке, после чего откроется пустое окно файла, которому программа присвоит название Untitled1 (безымянный №1), 2, 3... Это не имеет значения, так как далее мы изменим название.
Пусть пока эта "пустышка" побудет в таком виде, пока можно не обращать на нее внимания.
Одновременно с созданием "пустышки" программа предложит Вам создать проект.
Щелкайте по кнопке Yes.
Откроется окно New Project. Справа, под словом Directories, Вы увидите что-то типа "недоношенного" Виндовского проводника, а еще ниже окошко выбора дисков компьютера.
Выбираете тот диск, на котором у Вас находится MPLAB и в проводнике щелкаете по папке MPLAB, после чего Вы увидите ее содержимое.
Естественно, Вы увидите предварительно созданную нами папку Pic. Щелкните по ней, а затем по папке Multi.
После того, как Вы увидите, что она открылась (в том смысле, что картинка папки изменится с закрытой на открытую), переходим в левую часть окна New Project.
Снизу (List Files of Type) Вы увидите, что программа автоматически присвоит создаваемому файлу проекта расширение .PJT, поэтому в окошке File Name можно даже указать имя файла проекта без расширения (это относится и к файлам с расширениями .ASM и .HEX, о которых речь пойдет далее), а можно и с расширением - кому как нравится.
Проще и лучше всего присвоить всем файлам проекта одно и то же имя, в нашем случае - Multi.
Так как у всех файлов проекта разные расширения, то конфликтовать между собой они не будут.
Внимание: при создании проекта русские буквы применять нельзя, применяются латинские буквы и цифры. Все названия должны включать в себя не более 8-ми (включительно) символов (без учета символов расширения). Так как, в первую очередь, это относится к названиям файлов, то, автоматически, это относится и к названиям папок проектов (везде - Multi).
Итак, "настукиваем" в окошке File Name: Multi или Multi.pjt
Щелкаем по OK. Файл проекта с расширением .PJT cоздан и "ушел" в папку Multi.
Одновременно открывается окно Edit Project, в котором Вы увидите, что Вам предлагается создать "пустышку" HEX - файла, но перед этим, на всякий случай, нужно убедиться, что в окошке Divelopment Mode установлено: MPLAB SIM PIC16F84A (то, чем мы занимались в настройках), а в окошке Language Tool Suite установлено: Microchip.
Создаем "пустышку".
В списке Project Files щелкаем по строке multi(.hex). После этого строка выделяется и кнопка Node Properties... активизируется. Щелкаем по ней.
Открывается окно Node Properties. Менять здесь ничего не нужно (стоят настройки по умолчанию).
Единственное, что нужно сделать - убедиться, что в окошке Node установлено: MULTI.HEX, а в окошке Language Tool установлено: MPASM.
Щелкаем по OK. "пустышка" HEX - файла создана и "ушла" в папку Multi.
Опять появится окно Edit Project.
Теперь создаем "пустышку" ASM - файла.
Щелкаем по кнопке Add Node... Раскрывается окно Add Node. Это то же самое , что и окно New Project, только предназначенное для создания ASM - файла, поэтому нужно сделать в точности то же самое, что и в окне New Project.
Единственное различие может быть в том, что, если Вы указываете название файла с расширением, естественно, оно должно быть не .PJT, а .ASM
Чтобы не ошибиться, настучите Multi.asm и дело с концом.
Щелкаем по OK. "Пустышка" ASM - файла создана.
Опять появится окно Edit Project. В списке этого окна Вы увидите, что к HEX - файлу добавился ASM - файл.
Щелкните по OK. Окно Edit Project закроется и Вы останетесь "один на один" с пустым файлом Untitled.
Необходимо сделать его пригодным для работы по составлению текста программы (следовательно, речь идет о файле с расширением .ASM).
Для этого необходимо назвать его Multi.asm и "вписать" его в ранее созданную "пустышку" ASM - файла (см. выше).
Делается это так: щелкаем по слову File в главном меню, а затем - по строке Save As.. в выпадающем списке.
Открывается уже знакомое нам окно, только с названием Save File As. Выбираем папку Multi и определяем название файла (Multi.asm).
Здесь Вы еще увидите нечто, перед которым можно поставить или не поставить галочку. Ничего менять не нужно.
Щелкаем по OK и опять остаемся наедине с пустым файлом, только теперь он "прописан" в папке Multi под названием multi.asm (см. адресную строку окна файла).
Обратите внимание: и в списке окна Edit Project, и в адресной строке созданного нами ASM - файла, большая буква M в названии файла Multi.asm "превратилась" в маленькую - m.
Пусть Вас это не смущает, так как "что в лоб, что по лбу - все едино".
Особо дотошные могут работать только в нижнем регистре, а я, в этом смысле, привык работать по старинке. Каюсь, грешен, но ничего с собой поделать не могу.
Итак, проект Multi создан и перед Вашими глазами сейчас находится "поле предстоящих сражений" (файл Multi.asm). Именно на этом пока пустом белом листе мы и будем заниматься тем, что называют программированием.
В завершении темы проекта необходимо потренироваться в его закрытии и открытии.
Сейчас проект у нас открыт. Для того, чтобы закрыть проект, щелкните в главном меню по слову Project и выпадающем списке щелкните по строке Close Project (закрыть проект).
При первом закрытии, программа может ничего не спросить, но при последующих закрытиях проекта она может спросить: сохранить или нет внесенные изменения? Тут, как говорится, дело хозяйское.
В этом случае нужно щелкнуть или по Yes или по No и проект закроется.
Для того, чтобы открыть проект, щелкните в главном меню по слову Project и в выпадающем списке щелкните по строке Open Project.
Откроется знакомое Вам окно, только с названием Open Project, в котором нужно указать папку, в которой находится искомый проект (в нашем случае - папка Multi).
После открытия этой папки в списке (под окошком File Name) появится название файла проекта.
Если это то, что нужно, необходимо щелкнуть по строке с названием файла проекта, а затем - по OK.
Окно Open Project закроется и, в нашем случае, появится предупреждение, что HEX - файл "пустой", то есть, текст программы не ассемблировался, что и соответствует действительности.
Говорим спасибо программе за бдительность и щелкаем по OK.
После этого проект откроется и Вы увидите, в нашем случае, пустой файл Multi.asm во всей своей "боевой готовности".
Потренируйтесь. Создайте несколько своих пустых проектов в папке Pic.
Примечание: если производилось ассемблирование (HEX - файл не пустой) указанное выше предупреждение программой не выдается и ASM - файл открывается сразу.
Если после работы с текстом программы производилось ассемблирование и сразу же после этого проект закрывается, то он закроется сразу после ответа на вопрос программы: сохранить изменения или нет?
Если после последнего, перед закрытием проекта, ассемблирования в текст программы были внесены изменения, программа напомнит Вам о необходимости ассемблирования перед закрытием проекта, проведет его (щелкнуть по Yes), опять задаст вопрос: сохранить изменения или нет, и после ответа на него закроет проект.
Прочитал написанное и сам удивился: не думал, что объяснение такой относительно несложной процедуры потребует столько много слов. Проще не получается. Надеюсь, что не смотря на это, объяснение, все-таки, получилось доходчивым и понятным.
На самом деле, после того как Вы "вживетесь" в это дело, открытие проекта будет занимать у Вас не более 1 - 1,5 минут и Вы сами убедитесь - как это просто.
Загрузка в текстовый редактор текстов программ.
 Еще раз напоминаю, что речь идет о загрузке файлов с расширением .ASM
Варианты загрузки:
Если нужно просто просмотреть текст той или иной программы или "заготовки" тех или иных подпрограмм.
Например, Вы скачали в Интернете ASM - файл какой-нибудь программы и хотите с ней разобраться (например, в обучающих целях).
В этом случае, проекта создавать не нужно, то есть, эту манипуляцию можно произвести вне проекта.
Если нужно, кроме указанного выше, еще и создать HEX - файл этой программы (а также и просмотреть его), то, опять же, это можно сделать вне проекта.
Если нужно изменить текст этой программы с целью ее доработки, упрощения, оптимизации и т.д. и последующей отладкой (то есть, Вы хотите на ее основе создать нечто свое), то необходимо создать новый проект и загрузить текст этой программы в пустой ASM - файл проекта.
Могут быть и другие необходимости, но, в любом случае, работа программиста либо начинается с загрузки того или иного ASM - файла в текстовый редактор MPLAB, либо в дальнейшем "упирается" в необходимость такой загрузки.
Давайте посмотрим как это происходит на практике.
Для работы нам необходим какой-нибудь ASM - файл. В его качестве я буду использовать файл уже знакомой Вам программы Multi.asm
Примечание: ранее Вы скачали файл этой программы. Он предназначен для открытия в Вордовском редакторе из комплекта "Офиса" (расширение .DOC) и был использован мной только для ознакомления с текстом программы людей, не имеющих MPLAB.
Естественно, файл с таким расширением не пригоден для работы с MPLAB.

А вот Вам и "родной" MPLABовский файл программы Multi: здесь.
Скачайте его, например, в папку "мои документы" (на рабочий стол не закачивайте, так как проводник MPLAB его не "видит", то есть, Вы не сможете его открыть в MPLAB). С ним и будем работать.

Далее все файлы текстов программ и подпрограмм будут иметь расширение .ASM
Итак, открываем файл Multi.asm вне проекта.
Запустите MPLAB.
Если программа предложит Вам открыть какой-то более ранний проект MPLAB, то откажитесь, щелкнув по No.
Перед Вами - пустое окно программы MPLAB.
Щелкните по слову File в главном меню программы и далее - по строке Open в выпадающем списке.
Откроется знакомое Вам окно MPLABовского проводника, только с названием Open Existing File.
Находите то место, где у Вас лежит файл Multi.asm
После этого Вы увидите его название в списке проводника. Щелкните по строке с этим названием, а затем - по OK (или двойной щелчек по строке).
После этого Вы увидите окно с текстом программы.
Так как предполагается работа, связанная с изменением текста этой программы, и мы заранее создали под нее проект, то необходимо загрузить текст программы в проект.
Делается это так:
Не закрывая окно с текстом программы, открыть проект Multi. Вы раньше это уже делали, так что сделайте это сейчас самостоятельно.
При появлении сообщений об ошибках Project Error и Import Error (что естественно, так как ASM - файл проекта пустой и ассемблирования не производилось), необходимо их убрать, щелкнув по OK.
После этого откроется второе окно заготовки ASM - файла проекта.
Естественно, оно пустое и нужно скопировать в него содержимое окна, которое находится за ним (перенести содержимое рабочего файла Multi.asm в проект).
Переключения между окнами и сам процесс копирования ничем не отличается от того же самого при работе в "Виндах". Копирование производится через буфер обмена.
А именно: активизируете окно с текстом программы. В главном меню MPLAB щелкаете по слову Edit и в выпадающем списке щелкаете по Select All (выделить все).
Далее опять щелкаете по Edit, а в выпадающем списке - по Copy (текст программы ушел в буфер обмена).
Затем активизируете пустое окно. Далее - опять щелкаете по Edit, а в выпадающем списке - по Paste (вставить).
Все, копирование текста программы Multi.asm в проект произведено, и Вы видите этот текст в бывшей заготовке (пустышке) ASM - файла проекта.
Таким образом, пустышка ASM - файла "ушла в небытие", и мы имеем полноценный ASM - файл проекта, с которым можно работать.
После этого, окно с текстом программы, с которого производилось копирование, становится ненужным и его можно убрать обычным для "Виндов" способом, то есть, щелкнув по кнопке "Закрыть" в правом верхнем углу этого окна.
Далее, для удобства восприятия, разверните оставшееся окно, щелкнув по кнопке "Развернуть" в правом верхнем углу этого окна.
Сейчас Вы видите текст программы Multi.asm в полной своей "боевой готовности".
Так как этот текст в ПИК не зашьешь, то начинаем разбираться с HEX - файлами.
В проекте у нас лежит пустышка HEX - файла.
Для того, чтобы она превратилась в полноценный HEX - файл (то есть, наполнилась содержанием), необходимо проассемблировать текст программы. Текст у нас в наличии, так что никаких преград для выполнения этого действия нет.
Создание HEX - файла
 Ассемблирование выполняется очень просто:
В главном меню MPLAB щелкаете по слову Project, а в выпадающем списке - по строке Build Node.
Далее - ни во что не вмешивайтесь, а только ждите окончания процесса, программа все сделает в наилучшем виде.
В конечном итоге, Вы увидите окно Build Results с текстом, последняя строка которого должна выглядеть так: Build completed successfully.
Это соответствует случаю безошибочного составления текста программы. После этого окно Build Results нужно закрыть.
Если в тексте программы будут ошибки, то в окне Build Results будет выведен их список, а содержание последней строки будет иным.
Разбор этого варианта будет ниже.
Итак, сообщение об отсутствии ошибок мы получили, что говорит о том, что пустышка HEX - файла проекта превратилась в полноценный HEX - файл (Multi.hex), который можно загружать в программу, обслуживающую программатор, а значит - и зашить в ПИК.
Желающие (имеющие возможность) могут это сделать. Получится автоколебательный мультивибратор со стабильной частотой 5кГц.
Итак, Вы приобрели для себя новую возможность. Теперь, имея текст любой программы или файл с расширением .ASM, Вы можете самостоятельно создать файл "прошивки" для ПИКа, а это уже не мало.
Даже не имея ASM - файла, а имея неоформленный текст программы (например, взятый из какой-нибудь книги), Вы можете его "настучать" в текстовом редакторе MPLAB и, после этого, создать HEX - файл этой программы. Для этого даже не нужно быть программистом в буквальном смысле этого слова.
Программист же, должен описанные Выше операции вообще производить автоматически и знать их как таблицу умножения, поэтому - тренируйтесь. При составлении программ мозги не должны сильно отвлекаться на такие технологические вещи. Для них и так, кроме этого, будет достаточно и другой, более значимой, работы.
Работа с текстом программы при наличии ошибок
Посмотрим, что произойдет, если в тексте программы будет ошибка.
Искусственно введем ее, например, поставим точку с запятой перед названием регистра Sec в "шапке" программы Multi.asm (вспомните: MPLAB ничего "не видит" после точки с запятой).
Ассемблируем текст программы и получаем сообщение о 4-х ошибках с нижней строкой в конце, в которой сообщается о "неудачном" ассемблировании.
Если Вы сделаете двойной щелчок по строке с ошибкой, то курсор покажет Вам эту строку в тексте программы.
Так как ошибка заключается в том, что регистр Sec не прописан в "шапке" программы, то ошибочными будут считаться все строки программы, в которых упоминается регистр Sec.
Это и есть объяснение того, что количество ошибок, в данном случае, равно четырем.
Уберите точку с запятой перед регистром Sec и, после этого, еще раз проассемблируйте текст программы. Вы получите сообщение о безошибочном ассемблировании.
Введем еще одну ошибку. Предположим, Вы ошиблись при наборе текста программы и вместо команды decfsz (в подпрограмме Pause_1) "настучали" decfsx (нажали соседнюю с Z кнопку клавиатуры).
Введите эту ошибку и затем проассемблируйте текст программы.
Вы получите сообщение о 2-х ошибках: грамматическая ошибка в слове команды и в подпрограмме Pause_1 (в которой находится ошибочная команда) имеется ошибка.
Пощелкайте по сообщениям об ошибках, и Вы увидите, что, в первом случае MPLAB покажет на строку с ошибочной командой, а, во втором случае, на строку, в которой находится название подпрограммы.
Исправьте ошибку и проассемблируйте текст программы. Вы получите сообщение о безошибочном ассемблировании.
Третий случай: например, Вы забыли указать номер бита, к которому обращается бит - ориентированная команда BCF при формировании нулевого уровня на выводе RB0, и вместо bcf PortB,0 "настучали" bcf PortB
Введите эту ошибку в текст программы и проассемблируйте его.
Вы получите сообщение об ошибке типа: в функции отсутствует аргумент (то есть, чего-то не хватает).
Устраните ошибку и проассемблируйте текст программы. Вы получите сообщение о безошибочном ассемблировании.
При проведении такого рода экспериментов обратите внимание на изменение цвета полосы загрузочного индикатора, в конце процесса ассемблирования, с зеленого на красный (в случаях наличия ошибок).
В случаях отсутствия ошибок, цвет полосы не меняется.
Можно привести еще множество подобного рода примеров. Если есть желание, то самостоятельно введите другие ошибки и посмотрите, какие сообщения Вам выдаст MPLAB. Не забывайте только потом исправлять их.
Смысл сказанного выше: если Вы в тексте программы допустили ошибку, то, при его ассемблировании, MPLAB укажет Вам на нее и подскажет тип ошибки.
Устранением ошибок MPLAB не занимается, это личное дело программиста.
Обращаю Ваше внимание на следующее: в смысле обнаружения ошибок в тексте программы, MPLAB не всесилен. Он не может обнаружить ошибки, связанные с неправильным замыслом программы и стратегией ее построения. Это дело программиста.
MPLAB контролирует только "технологию" текста программы: наличие или отсутствие грамматических ошибок, соблюдение или нет синтаксических правил написания программы, правил переходов, указания аргументов и т. д.
Сообщение об отсутствии ошибок означает только соблюдение этих "технологических" правил и не является гарантией реализации замыслов программиста по той простой причине, что "технология" может быть соблюдена, но, при этом, замысел программы может быть неправильным.
Самый простой пример - зависание программы (уход в "вечное" кольцо внутри полного цикла программы).
Таким образом, для того чтобы создать полноценную рабочую программу, программист должен реализовать жизнеспособный замысел программы при условии соблюдения всех "технологических" правил ее составления.
Пока мы занимаемся только "технологией", так как без нее невозможна реализация любого замысла.
Если кто-то из Вас "рвется в бой" (в смысле желания побыстрее перейти к творчеству), советую "обуздать свои порывы" и заняться, пусть не самой интересной, но абсолютно необходимой рутинной работой, которую я Вам предлагаю, иначе Вам гарантирован повышенный процент неудач.
Разбираемся с симулятором
Прежде всего, симулятор позволяет заглянуть в "начинку" ПИКа и увидеть, что там происходит при выполнении команд программы.
При этом следует четко осознавать, что программист будет видеть "начинку" не реального ПИКа, а того виртуального ("идеального") ПИКа, который создается программой MPLAB (симулятором).
Если выбран PIC16F84A, то нашему вниманию и будет предоставлена "начинка" виртуального PIC16F84A, работающая по всем законам реального PIC16F84A.
Если реальный ПИК, для того чтобы заложить в него программу, нужно "прошить" в программаторе, то виртуальный ПИК "прошивается" автоматически при ассемблировании и какого-либо внешнего устройства, типа программатора, для этого не нужно.
Кроме того, что в симуляторе имитируется "начинка" ПИКа, он может имитировать и действия с этой "начинкой" в целях отладки программы или нахождения ошибок ее замысла программистом, что значительно повышает ценность симулятора при неудачах в реализации того или иного замысла программы.
Таким образом, сначала решаются "технологические" проблемы (при ассемблировании), а когда они решены (сообщение о безошибочном ассемблировании), программист переходит к решению проблем замысла программы и отладки характеристик (например, временных) с помощью симулятора.
В подавляющем большинстве случаев, программа, прошедшая "горнило" симулятора ("прогнанная" через него во всех возможных режимах), будет нормально работать и в реальном ПИКе.
Давайте заглянем в "начинку" виртуального PIC16F84A.
Программа Multi.asm проассемблирована. Это означает, что в виртуальный ПИК уже "зашита" программа Multi и с ней можно работать в симуляторе.
Обратите внимание на группу из 4-х кнопок в правой части строки с пиктограммами.
Это кнопки с надписями ROM, RAM, SFR и кнопка с нарисованными на ней очками.
Если проект Multi у Вас не открыт, то откройте его. Ассемблирования производить не нужно (если последнее ассемблирование было безошибочным и после него в текст программы не вносились изменения. Если это не так, то проассемблируйте текст программы, можно даже по принципу: на всякий случай).

Щелкните по кнопке ROM.
Откроется окно Program Memory Window, в котором:
1-й столбец: номер адресов памяти программ по порядку.
2-й столбец: адреса памяти программ в порядке их возрастания в 16-ричной системе исчисления, начиная с нулевого.
3-й столбец: пока (да и потом тоже) нам не нужен. Не обращайте на него внимания.
4-й, 5-й и 6-й столбцы: собственно говоря, сама программа в "чистом" виде, то есть, без всего того, что расположено после точек с запятой.
То, что Вы видите, есть содержимое памяти программ с указанием адресов и порядковых номеров команд рабочей части программы. Напоминаю, директивы в память программ не закладываются. Они работают внутри MPLAB, так что ни одной из них Вы здесь не обнаружите.
Примечание: естественно, в память программ ПИКов закладываются не команды на языке ассемблер, а машинные коды этих команд, до которых нам нет никакого дела.
Именно по этим адресам и в такой последовательности будут располагаться команды программы Multi в памяти программ реального PIC16F84A после его "прошивки".
В этом окне можно посмотреть, какое количество слов занимает в памяти программ программа (в нашем случае - 20) и каков обьем памяти программ PIC16F84A (1024 слова).
Закройте окно Program Memory Window.

Щелкните по кнопке RAM. Откроется окно File Register Window. В нем Вы видите область оперативной памяти по образу и подобию скачанного Вами ранее файла "Область оперативной памяти".
Возьмите этот лист (если есть распечатка) и сравните его содержимое с содержимым окна File Register Window.
Вы увидите одну и ту же картину, только с различными, взаимно дополняющими друг друга, формами наполнения.
Совместно используя окно File Register Window и распечатку "Области оперативной памяти" Вы можете свободно ориентироваться в области оперативной памяти.
Например, сверяясь с распечаткой "Область оперативной памяти", Вы узнаете в окне File Register Window значение содержимого того или иного регистра специального или общего назначения на данный момент времени (на момент выполнения той или иной команды программы).
Напоминаю, что все регистры в области оперативной памяти - 8-битные и поэтому значение их содержимого будет находиться в числовом диапазоне от 00 до FF (от 0 до 255 - в десятичной системе исчисления).
В окне File Register Window, числовые значения содержимого всех регистров отображаются только в 16-ричной форме исчисления. Таким образом, в окне File Register Window можно отслеживать изменения содержимого всех регистров области оперативной памяти при пошаговом исполнении программы (об этой возможности - ниже).
Обратите внимание на следующую особенность: если, после выполнения той или иной команды, произошло изменение содержимого того или иного регистра, то, до момента исполнения следующей команды программы, это содержимое будет выделено красным цветом.
Если изменений содержимого регистров не происходит, то оно (содержимое) будет выделено синим цветом.
Таким образом, при пошаговом исполнении программы, сразу становится видно, в содержимом какого именно регистра произошло изменение и какое это изменение конкретно.
Пример: в регистр Sec, расположенный в оперативной памяти, например, по адресу 0Сh, командой movwf Sec из регистра W пересылается (копируется) заранее заложенная в него константа, например .100 (вспоминаем: число 100 в десятичной системе исчисления).
Пусть до момента исполнения команды movwf Sec в регистре Sec находилось, например, число 0 (вспоминаем для тренировки: любое число до 9-ти включительно можно указывать в команде в "чистом" виде, то есть без атрибутов систем исчисления).
При этом в окне File Register Window Вы увидите: до выполнения команды movwf Sec, по адресу 0Ch будет находиться число .00 выделенное синим цветом.
В момент исполнения этой команды, число .00 заменится на число 64, которое будет выделено красным цветом.
Если следующая команда программы не производит изменения содержимого регистра Sec, то при выполнении этой команды число 64 изменит свой цвет с красного на синий.
Почему 64, а не 100? Вот Вам и типичный пример перевода чисел из одной системы исчисления в другую (из 10-чной в 16-ричную). Откройте имеющуюся у Вас таблицу перевода чисел из одной системы исчисления в другую и убедитесь, что числу .100 в 10-чной форме исчисления соответствует число 64h в 16-ричной форме исчисления.
Если Вы хотите устранить этот "разнобой", то в командах программы применяйте числа в 16-ричной системе исчисления. Это - кому как удобнее.
Закройте окно File Register Window.

Щелкните по кнопке SFR.
Откроется окно Special Function Register Window.
Если в предыдущем окне Вы видели содержимое всех регистров области оперативной памяти в 16-ричной системе исчисления, то в этом окне Вы видите содержимое только регистров специального назначения во всех трех системах исчисления.
Окно Special Function Regisrer Window можно использовать в качестве конвертора систем исчисления для чисел, зафиксированных в регистрах специального назначения на тот или иной момент выполнения программы, причем для всех сразу.
Оно может быть "палочкой - выручалочкой" для начинающих.
Программисты же, которые "набили себе руку", этим окном пользуются не часто, а, в основном, работают в окне File Register Window.
Описанное выше выделение цветом работает также и в окне Special Function Register Window.
Закройте окно Special Function Register Window.

Щелкните по кнопке с нарисованными на ней очками.
Откроется окно Add Watch Symbol. Если программист выборочно хочет видеть содержимое только тех регистров, которые его интересуют, то это как раз тот случай.
В окне Add Watch Symbol Вы увидите список всех регистров специального назначения и "прописанных" регистров общего назначения, плюс регистр W.
Названия регистров расположены в нем в алфавитном порядке.
Например, нас интересует содержимое регистра PortB.
Для этого нужно найти в списке название регистра и щелкнуть по строке с этим названием.
После этого кнопка Add (добавить) активизируется. Щелкнуть по этой кнопке, а затем - по кнопке Close.
Окно Add Watch Symbol закроется и Вы увидите, что в окне Watch (перед этим оно было на заднем плане) появятся "паспортные данные" регистра PortB.
Таким образом, по желанию программиста, в окне Watch можно сформировать список из любого количества задействованных в программе регистров с целью дальнейшего контроля за их содержимым.
Примечание: обратите внимание, что последним в предлагаемом списке является регистр W (аккумулятор).
Его содержимое можно просмотреть только в окне Watch (естественно, после его выбора и добавления в рабочий список).
Содержимое регистров отображается в окне Watch только в 16-ричной форме исчисления.
Выделение цветом (см. выше) также работает.
Лично я жму на кнопку с очками только в том случае, если мне нужно увидеть содержимое регистра W. Содержимым остальных регистров я интересуюсь в окне File Register Window.
Вы можете работать по-другому, это личный выбор программиста, а также вопрос привычки.
При закрытии окна Watch, программа спросит Вас: нужно или нет сохранить файл с данными этого окна.
Жмите на No. Проще и быстрее еще раз, при необходимости, выбрать то, что нужно, чем "возиться" с открытием этого файла.
Режимы работы симулятора, точки остановок

Механизм выполнения отладки программы имеет 2 режима: режим пошаговой отладки и режим автоматической отладки.
Обращаю Ваше внимание, что речь идет именно о механизме отладки (неких технологических удобствах), а не о самом процессе отладки.
Органы управления
Откройте проект Multi и обратите внимание на отдельную группу из 5-ти пиктограмм, в строке пиктограмм, со светофорами (красным и зеленым).
Четвертую слева пиктограмму (следы с желто-оранжевой полосой) пока игнорируем, без нее вполне можно обойтись. Остается 4 кнопки (пиктограммы). Это то, что нам сейчас нужно.
Запуск механизма отладки производится последовательно, начиная с команды Start (нулевой адрес в памяти программ).
Слово "последовательно" означает, что нельзя, например, начать отладку с середины программы.
Чтобы это сделать, необходимо до этой середины сначала "дойти", то есть, полноценно исполнить все предшествующие команды программы.
И в самом деле, если сразу начать отладку программы с ее середины, теряется раскладка содержимого регистров на момент начала отладки (она создается предшествующими командами), поэтому из такого рода отладки просто ничего не получится.
Итак, отладка начинается с команды Start и все команды программы, до первой команды участка (сектора) отладки, должны быть полноценно исполнены.
Именно для выхода симулятора на начало программы (Start) и используется крайняя правая кнопка указанной выше группы пиктограмм.
Я, честно говоря, так и не понял, что нарисовано на этой кнопке, поэтому я ее буду называть кнопкой сброса программы на начало.
Щелкните по этой кнопке (или нажмите клавишу F6 клавиатуры, она дублирует эту кнопку).
После этого Вы увидите, что строка с командой goto Start (осуществить безусловный переход в ПП Start), в конце "шапки" программы, выделится.
Обращаю Ваше внимание, что эта выделенная команда занимает нулевой адрес в памяти программ, в чем Вы можете убедиться, щелкнув по кнопке (пиктограмме) ROM.
После этого, в окне Program Memory Window, Вы увидите, что строка с командой goto Start выделена.
Возьмите это на заметку и закройте окно Program Memory Window.
При нажатии на кнопку сброса, Вы всегда будете наблюдать такую же картину, больше эта кнопка ничего делать "не может".
Итак, мы "встали" на начало программы, и теперь, "по закону жанра", необходимо ее исполнить в симуляторе.
Исполнить программу можно и в пошаговом, и в автоматическом режиме.
Пошаговый режим исполнения программы применяется в тех случаях, когда нужно "капитально" разобраться с содержанием и последствиями выполнения тех или иных команд (или всех команд) выбранного "сектора обстрела" (участка программы).
Автоматический режим исполнения программы применяется для быстрого выхода на начало этого "сектора" (с быстрым, но неконтролируемым исполнением всех предшествующих команд программы - то, о чем говорилось выше), а также для быстрого определения временных характеристик программы (об этом - ниже).
Слово "быстрое" означает, что команды выполняются быстро (определяется быстродействием Вашего компьютера), но, при этом, естественно, невозможно визуально отследить содержимое регистров и порядок исполнения команд.
Для того, чтобы четко определить в программе границы автоматического режима исполнения программы, программист может назначить так называемые точки остановки.
Точкой остановки считается помеченная программистом строка программы, содержащая команду, название подпрограммы, метку.
Как только "дело доходит" до исполнения помеченной строки, исполнение программы останавливается и помеченная строка выделяется.
Ближе к практике. Начнем с пошагового режима.
Орган управления - кнопка пошагового исполнения программы с нарисованными на ней следами (третья слева).
Загляните в проект Multi (в текстовый редактор). После нажатия на кнопку сброса, мы находимся на начале программы.
Один раз щелкните по кнопке пошагового исполнения программы.
Произойдет переход (в соответствии с исполненной командой goto Start) в рабочую часть программы на строку с названием ПП Start.
Щелкните еще раз. Выделится строка следующей команды и т.д.
А теперь пощелкайте по "следам" (или по кнопке F7 клавиатуры, что одно и то же) "от души".
Вы увидите порядок исполнения программы. В любой момент Вы можете нажать на кнопку сброса и начать исполнять программу снова.
Пока "вживитесь" в сам механизм пошагового исполнения программы, а что при этом будет происходить с содержимым регистров - разберемся позднее.
В процессе этого "вживления" у Вас, естественно, возникнут вопросы, ответы на которые Вы найдете ниже.
Автоматический режим исполнения программы
. Прежде чем работать в автоматическом режиме, необходимо назначить точку (точки) остановки.
Если этого не сделать, то программа будет исполняться "вечно" (симулятор зависнет).
В простейшем случае, точку остановки можно назначить прямо в тексте программы.
Делается это так: щелкните правой кнопкой мыши по строке, например, с командой decfsz Sec,F в подпрограмме Pause_2 (можно по самой команде, можно по названию подпрограммы, а можно и по пустому месту, короче, в любом месте выбранной строки до точки с запятой).
Появится окно со списком из четырех позиций.
Далее, левой кнопкой мыши щелкаете по самой верхней строке в этом списке (Break Point).
Список исчезнет, и все, что находится в этой строке выделится красным цветом (индикатор точки остановки).
Все, точка остановки выставлена, и Вы с комфортом можете ее наблюдать в тексте программы.
Сбросьте программу на начало (см. выше) и, для того, чтобы запустить режим автоматического исполнения программы в симуляторе (берите на заметку), щелкните по кнопке с зеленым светофором.
На время исполнения программы, самая нижняя строка окна программы MPLAB (в начале которой стоит Ln...) окрашивается в желтый цвет.
В данном случае, исполнение программы происходит так быстро, что момент окрашивания можно и не заметить. Учтите это.
Вообще же, эта желтая полоса является как бы индикатором рабочего процесса исполнения программы в симуляторе в режиме автоматического исполнения программы и по ней можно судить, зависла ли программа (желтое выделение не снимается) или нет.
В нашем случае зависания нет и программа в симуляторе будет исполнена с ее начала и до назначенной точки остановки, что Вы сейчас и наблюдаете: программа остановилась на указанной нами команде (точке остановки).
Потренируйтесь: несколько раз сбросьте и запустите автоматическое исполнение программы и обратите внимание на желтую полосу в нижней части окна MPLAB.
Зачем этот "автомат" нужен?
Предположим, из всей программы нам, на данном этапе отладки, необходимо произвести отладку подпрограммы Pause_2.
Если мы будем работать в пошаговом режиме, то нам, для того чтобы добраться до ПП Pause_2, придется долго и нудно добираться до первой ее команды вручную, что, согласитесь со мной, не совсем удобно.
А в "автомате" это делается быстро. Вот Вам и ответ.
Напоминаю, что в "автомате" программист не имеет возможности контролировать содержимое регистров, эти дела делаются при пошаговом исполнении программы.
Далее можно капитально отладить ПП Pause_2 и в пошаговом режиме.
Если, например, нужно после этого перейти к отладке ПП Pause_1, то нужно снять (или не снимать, если она потребуется в дальнейшем) старую точку остановки и назначить новую.
Снятие точки остановки.
В главном меню MPLAB щелкните по слову Debug, а затем по строке Clear All Points (удалить все точки остановки) в выпадающем списке.
Появится окно с вопросом: "Вы действительно хотите удалить все точки остановки?" Щелкайте по "Да".
После этого красный цвет выделения точек остановок меняется на обычный (черный) цвет.
Еще один способ установки точек остановки
. В главном меню MPLAB щелкните по слову Debug, а затем по строке Break Settinds в выпадающем списке.
Откроется окно Break Point Settings.
В списке этого окна Вы увидите содержимое первого столбца текста программы, с которой Вы работаете.
Этот способ установки точек остановки удобен тем, что можно заранее указать, на какой из подпрограмм или меток нужно остановиться и включать или выключать те или иные точки остановки по своему усмотрению.
В этом случае, поставить точку остановки на строке программы, не содержащей названия подпрограммы или метки, нельзя (в отличии от описанного выше способа).
Щелкните по кнопке окошка Start в окне Break Point Settings.
Выпадет список. Щелкните, например, по строке Pause_1 в этом списке. Название Pause_1 "ушло" в окошко.
То же самое проделайте и с окошком End.
Щелкните по кнопке Add.
В большом списке окна Break Point Settings появится строка с галочкой.
Если галочка стоит, то данная точка остановки включена, если нет - выключена.
Включение и выключение происходит при щелчке по квадратику с галочкой.
При снятии точек остановки (Debug - Clear All Points), снимается только галочка.
Чтобы, после этого, снова установить точку (точки) остановки, необходимо войти в окно Break Point Settings (см. выше) и установить там галочку (галочки).
Для того, чтобы удалить строку с точкой остановки из большого списка, необходимо ее выделить, а затем щелкнуть по кнопке Remove.
Большой список может содержать несколько точек остановок (вплоть до суммарного количества подпрограмм и меток).
Запустить "автомат" можно также, нажав на кнопку F9 клавиатуры.
Кнопка с красным светофором (или клавиша F5 клавиатуры, она ее дублирует) необходима для остановки "автомата" и, соответственно, пользоваться ей можно только в "автомате".
Если программа не работает и, при этом, зависла внутри какой-нибудь подпрограммы (желтое выделение снизу окна MPLAB не снимается), то, несколько раз остановив и запустив "автомат", можно достаточно точно определить: в какой именно подпрограмме или на каком участке программы или подпрограммы, программа, в симуляторе, "ушла в вечное кольцо".
"Уйти в вечное кольцо" может и вполне работоспособная программа если ее сбросить на начало, а затем запустить "автомат" (точек остановок не назначено). Самостоятельно проверьте это на практике, используя программу Multi.
Объяснение простое: точек остановки не выставлено, а программа Multi является циклической (точка входа в полный цикл программы приходится на первую команду подпрограммы Start).
Работоспособная программа или подпрограмма может также "уйти в вечное кольцо" в симуляторе, если их исполнение зависит от каких-нибудь внешних (по отношению к микроконтроллеру) сигналов, а в симуляторе эти сигналы, по ходу выполнения программы или подпрограммы, не симитированы.
Такого рода имитация - предмет отдельного разговора, а пока просто примите сказанное выше к сведению.
Секундомер
 Помните как мы подсчитывали количество команд и время их исполнения в программе Multi для того чтобы точно сформировать временной интервал одного полупериода (100м.ц. = 100мкс.)?
В такой простой программе подобные расчеты, конечно, можно произвести и на бумаге.
А если количество команд, включенных в цикл, равно не 5, 10, а 100, 200, ... и, к тому же, в состав этих команд включены циклические подпрограммы и команды переходов (что встречается достаточно часто)?
В этом случае Вы просто "утонете" в громоздких и сложных расчетах, которые отнимут у Вас массу времени.
Так как программа MPLAB для того и создана, чтобы облегчить труд программиста, она в своем составе имеет специальную утилиту, которая называется "секундомером".
При исполнении программы в симуляторе (в любом режиме), секундомер считает количество машинных циклов и тут же, в соответствии с установленной частотой кварца, переводит их в привычное для нас время (мкс., мс., с.).
В любой момент программист может сбросить показания секундомера на ноль и начать новый отсчет времени.
Для вызова окна секундомера небходимо щелкнуть по слову Window в главном меню MPLAB, а затем - по строке Stopwatch в выпадающем списке.
Откроется окно Stopwatch. Это и есть секундомер.
В нем Вы увидите установленную нами ранее частоту кварца, кнопку Zero (сброс секундомера на ноль) и квадратик с надписью Clear On Reset, в котором можно либо поставить, либо не поставить галочку.
Если галочка установлена, то секундомер будет автоматически сбрасываться в ноль при переходе на начало программы (сбросе программы), если не установлена, то такого сброса не будет. Лично у меня она установлена, что советую и Вам.
Сбросьте программу Multi на начало.
Пощелкайте по кнопке со следами ног (или по F7) несколько раз, и Вы увидите, что секундомер подсчитывает количество машинных циклов выполнения программы и тут же переводит их в микросекунды.
Примечание: обозначение микросекунды выглядит как us из-за того, что в английском алфавите нет буквы "мю" (эта буква - греческая), а английская буква u наиболее к ней близка по написанию (подсказка посетителя сайта).
Секундомер посчитает все "в лучшем виде", с учетом времени исполнения сложных команд, требующих для их исполнения не один, а два машинных цикла, и выдаст Вам готовый результат подсчета исполнения команд на том участке программы, на котором Вы работаете.
Практический вывод из этого следующий: если программисту необходимо подобрать значение константы для формирования какого-то конкретного интервала времени (например, задержки), то ему вовсе не обязательно скурпулезно подсчитывать на бумаге количество машинных циклов исполнения команд, а достаточно "грубой прикидки".
"Доводка до нормы" производится путем изменения в небольших пределах значения константы, а если этого недостаточно, то и установкой того или иного количества NOPов с постоянным контролем результатов изменений по секундомеру до получения нужного результата.
Сам по себе, этот процесс довольно-таки не сложен (нужно только "руку набить"), и таким образом можно, с минимальными трудозатратами, отстроить временные характеристики даже очень "навороченных" программ.
Если имеется чужая программа, и Вам необходимо внести свои коррективы в ее временные характеристики, то это тоже делается при помощи секундомера.
Естественно, что, для больших величин времени циклов, нужно работать в "автомате", иначе, то есть, в пошаговом режиме, Вы потеряете много времени.
Такого рода метод проб и ошибок, на практике, очень часто оказывается гораздо более эффективным, чем головоломка с вычислениями на бумаге (хотя, и это сбрасывать со счетов нельзя).
Переходите к следующему разделу.

Переход к оглавлению


Kea_26

Переход к оглавлению

6. Что дальше?

То, что Вы узнали (а я надеюсь и усвоили), изучая предыдущие разделы, есть основа "прожиточного минимума" программиста ПИКов, необходимая для того, чтобы иметь первичное представление о том, "с какого бока подойти к этой корове и за что дергать, чтобы она дала молоко".
В соответствии с ранее сформулированной тактикой обучения, я "оставил за скобками" то, без чего, на первых порах, можно обойтись. В дальнейшем это будет наверстано.
Если Вы хорошо потрудились, то, к настоящему времени, у Вас должна сложиться, хотя бы в общем виде, более - менее целостная картина предмета обучения.
В настоящее время, Ваши знания в области программирования ПИКов, вернее всего, в основном, технологичны и представляют собой набор достаточно разобщенных сведений.
Это вполне естественно и нормально.
Поздравляю Вас с преодолением самой нудной и не слишком интересной части работы (естественно, при условии, что Вы хорошо потрудились).
На данный момент, Вы имеете все необходимое для перехода к наиболее интересной, творческой, части работы, которая естественным образом поможет Вам превратить разобщенное в единое целое.
В этом отношении, ничего лучшего, чем пример поэтапной разработки конкретного устройства на ПИКе, не существует.
Далее, на такого рода примере, я постараюсь показать Вам весь путь от замысла такого устройства и до его практической реализации.
Устройство будет несложным, но, тем не менее, на примере его создания Вы, с моей точки зрения, многому научитесь.
Далее я еще приведу несколько подобных примеров, руководствуясь принципом постепенного возрастания сложности.
Дальше я пока не заглядывал, но имеется задумка создать какую-нибудь полезную программу (а, соответственно, и устройство под нее) прямо на Ваших глазах, с подробными комментариями этого процесса, показом всей "кухни от и до" и обсуждением различных вариантов.
Итак, начинаем "вгрызаться" в то, что имеем. Желаю Вам трудолюбия и терпения.
Переходите к следующему разделу.

Переход к оглавлению


Kea_27

Переход к оглавлению

7. Пример создания программы (начало).

Общие положения
 Порядок разработки устройств на микроконтроллерах следующий:
Предположим, возникла идея создания устройства на микроконтроллере, которая "упирается" в создание программы, обеспечивающей реализацию замысла конструктора.
Естественно, что программа создается не сама для себя, а под конкретное устройство, то есть, она, как бы, является "приложением" к процессу разработки электронной начинки устройства в том смысле, что участвует в создании этой начинки аппаратными средствами применяемого микроконтроллера с привлечением программных средств.
Проще говоря, основная часть электронной начинки задуманного устройства (кроме внешних, по отношению к микроконтроллеру, устройств) как бы "уходит" внутрь микроконтроллера, а программа формирует состав этой электронной начинки и обеспечивает необходимое для нормальной работы взаимодействие ее составных частей, в соответствии с логикой их работы и взаимодействия, определяемой программистом.
Специфика конструирования устройств на микроконтроллерах заключается в том, что, в большинстве случаев, наличия отработанной блок - схемы программы оказывается вполне достаточным для создания его полной принципиальной схемы или той части принципиальной схемы, которая включает в себя микросхему микроконтроллера.
И в самом деле, если осуществлена "жесткая привязка" внешних (по отношению к микроконтроллеру) цепей и устройств к конкретным выводам микроконтроллера (то есть, функции выводов определены), типы внешних цепей и устройств также определены и "привязаны" к рабочим уровням напряжения микроконтроллера на этих выводах, то он, как бы, представляет собой базовый блок принципиальной схемы с "четко" определенными функциями (по отношению к внешним цепям и устройствам), к выводам которого подключаются входы и выходы внешних устройств, которые (устройства), в процессе отладки аппаратуры могут видоизменяться или заменяться на аналогичные, при неизменных функциях базового блока.
Именно к такого рода определенности и нужно стремиться при конструировании устройств на микроконтроллерах.
Конечно, такую "привязку" не нужно рассматривать как нечто изначально "застывшее" и неизменное: в процессе работы она может быть скорректирована или изменена.
Процесс создания "электронной начинки" устройства можно разделить на два этапа, которые характеризуются словами "грубо" и "точно".
"Грубый" этап включает в себя создание блок - схемы устройства, в которой "центром вселенной" является базовый блок на микроконтроллере.
Блок-схема устройства состоит из блок-схемы программы (базовый блок) и блок-схемы внешних устройств, входы и выходы которых подключаются к выводам микроконтроллера.
На этом этапе программист должен ясно и четко представлять себе функции, выполняемые тем или иным "квадратиком" блок - схемы, специфику и порядок их взаимодействия.
Например, если в блок - схему программы введен счетчик, то необходимо, в первую очередь, знать: зачем он вообще нужен и каковы его функции в "общей картине" устройства.
После того, как Вы убедились, что без него обойтись нельзя, необходимо определить его рабочие параметры (направление счета, коэффициент деления, активный фронт и т.д.), необходимые в дальнейшем для составления программы.
Естественно, для составления блок - схемы (и вообще для дальнейшей работы) необходимо знание принципов работы устройств цифровой техники (а часто и аналоговой), иначе будет отсутствовать предмет работы с текстом программы.
По этой причине, "ставки" начинающих программистов, имеющих опыт практической работы с дискретными элементами цифровой техники, гораздо выше "ставок" начинающих программистов, которые не уделяли этому должного внимания, хотя, в конечном итоге, все зависит от трудолюбия и "упертости".
После того, как определены элементы блок - схемы программы и порядок их взаимодействия, определяются функции выводов микроконтроллера, то есть, конкретно определяется порядок подключения входов и выходов внешних устройств к выводам микроконтроллера в соответствии с их функциональным предназначением.
В дальнейшем, при составлении программы, этот порядок подключения будет определять содержание команд, производящих действия с данными, поступающими в микроконтроллер от внешних устройств, и с данными, которые формируются микроконтроллером для управления внешними устройствами.
После этого, сначала, составляется принципиальная схема базового блока, а потом, к определенным ранее выводам микроконтроллера, подключаются входы или выходы внешних устройств.
При этом, в простейшем случае, внешние устройства можно изобразить в виде элементов блок - схемы (если программист еще не определился с их принципиальными схемами или хочет, впоследствии, "подогнать" принципиальные схемы этих устройств под создаваемую программу).
Если программист заранее определился с принципиальными схемами внешних устройств, то можно составить полную принципиальную схему разрабатываемого устройства и "привязать" программу к специфике их работы.
Второй вариант, на мой взгляд, для начинающих, более предпочтителен, так как он предполагает изначальную продуманность конструкции устройства и меньшее количество непредвиденных "сюрпризов" при работе с текстом программы, хотя и первый вариант не плох, особенно для программистов с опытом работы.
На этом "грубый" этап работы программиста заканчивается и начинается "точный".
Итак, принцип работы устройства продуман, понятен, принципиальная схема в наличии.
Теперь можно начинать составление программы.
Оно начинается с "шапки" программы, в которой нужно "прописать" все регистры специального и общего назначения, которые предполагается использовать в программе.
Если Вы что-то забудете "прописать", то ничего страшного, MPLAB Вам об этом напомнит.
По ходу создания программы, может появиться необходимость "прописки" дополнительных регистров. Проблемы в этом никакой нет, они "прописываются" также, как и ранее "прописанные" и тут же "вводятся в бой".
Основные "сражения" с непослушными нулями и единицами развернутся в рабочей части программы.
Здесь будут "стрелять пушки, бить барабаны и литься кровь". Желаю Вам оказаться победителем в этом "сражении" и испытать радость победы.
Обычно рабочая часть программы начинается с подготовительных операций.
И в самом деле, принципиальная схема устройства в наличии, а значит можно осуществить "привязку" программы к внешним цепям и устройствам.
Например, если к выводу RB0 порта В подключен светодиод, а к выводу RA4 порта А подключен выход, например, формирователя импульсов, то в начале программы (обычно - в подпрограмме Start) необходимо вывод RB0 переключить на работу "на выход", а вывод RA4 переключить на работу "на вход".
Таким образом, речь идет о начальной "настройке" битов каких-то из регистров специального назначения.
Очень часто требуется сбросить в ноль содержимое каких-то из регистров общего назначения.
А вот после подготовительных операций начинается то, что описать, прямо скажу, сложновато.
Да и как можно описать детали игры, например, в шахматы?
Возможных комбинаций - миллионы, просто жизни не хватит.
Тема - неисчерпаемая. Для людей творческих и ищущих - настоящий "Клондайк", где можно развернуться "во всю ширь" и реализовать весь свой творческий потенциал.
Естественно, свою задачу я вижу не в том, чтобы "перелопачивать" все многообразие различных устройств и программ под них, здесь "каждый умирает в одиночку", а в том, чтобы помочь Вам приобщиться к этому очень перспективному и увлекательному занятию.
Если быть объективным, то заголовок этого раздела не в полной мере отражает суть того, чем мы будем далее заниматься.
Необходимо уточнить: мы будем заниматься конструированием устройств на ПИКах и созданием для этих устройств программ.
Для того чтобы это сделать, нужен "симбиоз" электронщика и программиста.
"Однобокость" типа "или электронщик, или программист" ни к чему хорошему не приведет, учтите это.
Так как "Самоучитель..." расчитан, в первую очередь, на начинающих, устройства, программы под них или фрагменты программ будут, поначалу, простыми, с постепенным возрастанием сложности.
Все файлы текстов программ или фрагментов программ будут иметь расширение .ASM и их можно будет открыть в MPLAB.
Подробное описание процесса конструирования устройств на PIC16F84A
Примечание: все, что будет описываться ниже, для меня уже пройденный этап и логические цепочки я, естественно, буду восстанавливать по памяти.
Постараюсь довести до Вас логику рассуждений и сделать это в удобной для восприятия форме.
Примеры взяты из реальной жизни.
Описываемые ниже устройства работоспособны, полностью выполняют свои функции и проверены в работе.
Пример № 1
 В большинстве случаев (но не всегда), все начинается с того, что возникает некая крайняя необходимость.
Я работаю в системе МЧС Липецкой области и занимаюсь вопросами технического обслуживания и ремонта средств радиосвязи и модернизации существующей системы радиосвязи.
При переходе на централизованную систему радиосвязи, возникла необходимость в создании относительно дешевого, двунаправленного ретранслятора на импортных симплексных радиостанциях Vertex - 2000, а также в переводе однонаправленного ретранслятора Vertex - 7000 в двунаправленный режим.
Естественно, под это дело нужна и оконечная аппаратура.
Все три задачи были успешно решены при помощи несложных устройств на PIC16F84A, которые стоят очень дешево и, без преувеличения, экономят десятки тысяч рублей, время и нервы.
Для начала, рассмотрим самое простое из этих устройств - устройство формирования сигнала тонального вызова для оконечной аппаратуры управления ретранслятором, в качестве которой использована радиостанция Vertex - 2000.
Начинаем "раскрутку".
Суть проблемы:
Система тонального вызова необходима для вызова корреспондента в том случае, если его трубка уложена в специально отведенное для этого место (в радиостанциях "Маяк" это пульт управления, а в радиостанциях "Виола" - блок микротелефона).
В таком положении, в режиме дежурного приема, радиостанция "не слышит" ничего, кроме строго определенного по частоте сигнала тонального вызова и "докричаться" до корреспондента с уложенной трубкой можно только послав ему сигнал тонального вызова.
Радиостанция Vertex - 2000 имеет систему тонального вызова, но ни одно из значений частот тонального вызова, которые предлагаются разработчиками и которые можно установить при программировании радиостанции, не соответствует значению применяемой в системе радиосвязи Липецкой области, частоте тонального вызова (1450гц.), а самая близкая к этому значению частота тонального вызова, которую можно установить в Вертексе, не попадает в частотный интервал допуска.
Так как возможности коррекции частоты тонального вызова в Вертексе не предусмотрено (можно только выбрать из списка), то возникает необходимость в создании устройства формирования сигнала тонального вызова с высокостабильной частотой 1450гц.
В простейшемм случае, управление работой этого устройства можно сделать ручным (при помощи кнопки вкл/выкл тонального вызова), но это уже "прошлый век" и не совсем удобно для пользователя.
Кроме того, в этом случае, время выдачи в эфир сигнала тонального вызова нестабильно и существует вероятность "недодачи" (по времени) этого сигнала, при которой приемник тонального вызова корреспондента, из-за своей инерционности, просто "не успеет" сработать, да и "перебор" тоже не нужен.
Следовательно, при включении на передачу, необходимо организовать автоматический режим выдачи в эфир сигнала тонального вызова в течение калиброванного интервала времени.
Так как для пользователя не всегда бывает удобным запуск устройства формирования тонального вызова каждый раз, когда происходит включение на передачу, то необходимо сделать так, чтобы режим автоматического тонального вызова можно было включить или отключить.
В итоге, к устройству формирования сигнала тонального вызова предъявляются следующие основные требования:
1. Частота сигнала тонального вызова должна быть высокостабильной и равной 1450гц.
2. Сигнал тонального вызова должен формироваться при каждом нажатии тангенты микрофонной гарнитуры.
3. После нажатия на тангенту, сигнал тонального вызова должен выдаваться в эфир в течении калиброванного интервала времени (оптимально подобранного по длительности).
4. Режим тонального вызова должен включаться и выключаться вручную.
Примечание: естественно, что первые 3 пункта должны выполняться только при включенном режиме тонального вызова.
Итак, "глобальные" требования сформулированы в конкретной и однозначно понимаемой форме.
Теперь начинаем "раскрутку" по пунктам.
Первый пункт
Если речь идет о высокостабильной частоте, то опорный генератор ПИКа должен быть кварцевым (если не требуется высокой стабильности частоты, то его можно перевести в режим RC - генератора).
В этом отношении "изобретать велосипед" не нужно. Будем использовать стандарт: стандартный кварцевый генератор (тип - XT) с кварцем на 4 мГц.
В этом случае, один машинный цикл равен одной микросекунде, что очень удобно при расчетах и "прикидках".
При использовании кварца, время выдачи в эфир сигнала тонального вызова также окажется высокостабильным, хотя высоких требований по стабильности к этому интервалу времени не предъявляется.
Как говорится, "кашу маслом не испортишь". Будем считать это приятной неожиданностью.
В принципе, можно сформировать сигнал любой скважности.
Исходя из того, что анализатор приемника тонального вызова корреспондента представляет собой цифровое устройство, некритичное к скважности импульсной последовательности, вырабатываемой на передающей стороне, то каких-то жестких условий по скважности нет, за исключением того, что импульсы должны быть "не слишком короткими и не слишком длинными" (учет инерционности радиотракта).
Особо не ломая над этим голову, определяем значение скважности (отношение длительности рабочего импульса к периоду) = 0,5 (так называемый "меандр").
В отношении процесса формирования периода импульсной последовательности сигнала тонального вызова (частоты) тоже мудрить не нужно: полупериоды формируем, используя закольцовку рабочей точки программы в подпрограммах задержки (см. текст программы Multi).
Все, что возможно определить на предварительной стадии разработки устройства, в части касающейся 1-го пункта ("грубая прикидка"), определено.
Для того чтобы, к моменту начала составления текста программы, было определено максимальное количество исходных данных, этим принципом нужно руководствоваться и в дальнейшем. Это - одна из составных частей продуманности любой программы.
Второй и третий пункты
Так как требования 2-го и 3-го пунктов между собой "завязаны", то определение исходных данных для их реализации лучше всего производить в комплексе.
Совершенно очевидно, что при нажатии на тангенту (включении на передачу) должен быть запущен какой-то механизм, формирующий сам сигнал тонального вызова и "дозирующий" по времени его выдачу в эфир.
Так как программа, в любом случае (кроме SLEEP), должна исполняться непрерывно, то, в случае нахождения радиостанции в режиме дежурного приема, совершенно очевидно, что программа должна "закольцеваться" (уйти в "вечное" кольцо - до последующего нажатия на тангенту) в какой - нибудь подпрограмме.
В большинстве случаев, такого рода "закольцовки" происходят в начале программы, а именно, в подпрограмме Start, но можно "закольцевать" программу и в другом месте (с учетом сказанного выше).
Опять же, не будем "изобретать велосипед", тем более, что в такой простой программе практически нет выбора для места этой "закольцовки".
Итак, мы определились: в режиме дежурного приема, программа "закольцовывается" (циклически исполняется) в подпрограмме Start и не выходит из нее до момента прихода управляющего сигнала включения на передачу (это то, что я называю "вечным кольцом". Хотя это определение, может быть, и не вполне удачно, но, тем не менее, оно короткое и, вцелом, отражает суть происходящего).
В момент прихода управляющего сигнала, рабочая точка программы должна выйти из этого "вечного кольца" по сценарию "программа исполняться далее".
А теперь прикинем, а что же должно быть далее?
Естественно, должен начаться процесс формирования одного периода тонального сигнала, соответствующего частоте 1450гц.
Предположим, мы его сформировали, а дальше?
Совершенно очевидно, что "закольцовка" рабочей точки программы внутри подпрограмм, формирующих период тонального сигнала, абсолютно необходима, иначе сформируется один период и на этом все закончится.
Если поставить время этой "закольцовки" в зависимость только от времени нажатия тангенты, то тональный сигнал будет выдаваться в эфир в течении всего времени нажатия тангенты, чего допустить нельзя.
После нажатия тангенты, тональный сигнал должен выдаваться в эфир в течение 3-х сек. (давайте, для определенности, "привяжемся" к этому значению, тем более, что именно такое значение времени выдачи в эфир сигнала тонального вызова является оптимальным и именно на него нужно ориентироваться).
Таким образом, становится очевидным, что процесс формирования сигнала тонального вызова должен быть поставлен в зависимость не только от сигнала включения на передачу, но и от некого таймера (счетчика времени), отмеряющего 3 сек. с момента нажатия на тангенту (перехода с приема на передачу).
Таймер должен что-то считать. Решение напрашивается само собой: считать нужно количество полных циклов работы подпрограмм формирования периода, а, проще говоря, количество периодов сигнала тонального вызова.
Закладывая в таймер различные значения констант, можно будет получить те или иные значения времени выхода в эфир сигнала тонального вызова, а, следовательно, и отрегулировать это время.
Выстраивая далее эту логическую цепочку, можно однозначно сформировать следующий принцип работы программы:
В режиме дежурного приема, рабочая точка программы должна уйти в "вечное кольцо" в подпрограмме Start и "наматывать в ней кольца" до момента переключения радиостанции с приема на передачу.
После того, как это случится, рабочая точка программы должна выйти из этого кольца и еще раз "закольцеваться" в подпрограммах формирования периода тонального сигнала.
Одновременно должен начать работать таймер, время срабатывания которого зависит от количества периодов тонального сигнала, сформированных программой с момента нажатия на тангенту.
После прохождения заданного константами количества периодов (суммарное время 3 сек.), таймер должен сработать (очиститься) и рабочая точка программы, независимо от того, находится ли радиостанция на передаче или нет, должна снова уйти в "вечное кольцо" подпрограммы Start до следующего рабочего перепада управляющего сигнала (до следующего перехода от приема к передаче).
Все это, с непривычки, выглядит чем-то не вполне понятным, но, после работы с текстом программы, многие из Вас удивятся - как это просто.
Для того чтобы еще более конкретно оформить конструкцию программы, необходимо выяснить: в каком месте программы должны закладываться константы, то есть числа, определяющие интервал времени выхода тонального сигнала в эфир?
Дело в том, а это очень важно и сейчас, и при дальнейшей работе с другими программами, что константы, используемые в работе устройств, подобных упомянутому выше таймеру (устройств, которые что-то подсчитывают), должны закладываться в эти устройства предварительно, то есть, до начала их работы.
Если, в нашем случае, команды, "рулящие" таймером, находятся внутри 3-х секундной "закольцовки", то константы для таймера должны закладываться вне этой "закольцовки", иначе значения констант будут обновляться через каждый период сигнала тонального вызова и таймер никогда не сработает (не очистится).
В нашем случае особенно выбирать не приходится, так как вне 3-х секундного кольца находится только подпрограмма Start, и задача сводится только к определению места, внутри этой подпрограммы, в которое можно "всунуть" команды закладки констант.
Так как эти команды не требуют никаких предварительных установок, то поместить их можно в любое место подпрограммы Start, "вклинившись" в любое место между командами подготовительных операций.
Так как такого рода "бардак" в тексте программы, затрудняющий комфортное восприятие текста программы, не желателен, то лучше всего поместить команды закладки констант в конце подпрограммы Start. Так и сделаем.
Четвертый пункт
Для реализации этого пункта, естественно, нужен какой-то орган управления типа переключателя или кнопки.
В данном случае, можно задействовать кнопку А (аксессуары) радиостанции Vertex - 2000, расположенную на передней панели радиостанции, что очень удобно.
Она задействуется при программировании радиостанции ("зашивке" частот и режимов), а в рабочем режиме, если при программировании ей не присвоено никаких функций (что и имеет место быть при целевом программировании радиостанции для выполнения поставленной задачи), она просто свободна и ее можно использовать в качестве выключателя (или "включателя") режима тонального вызова.
Если использовать непосредственно кнопку (фиксации нет), то придется назначить, в качестве элемента оперативной памяти, дополнительный регистр общего назначения, работающий как триггер, состояния которого зависят от состояния клавиатуры (кнопки) на момент опроса.
Кроме того, если речь идет о более - менее качественном устройстве, необходимо будет принять меры по защите от дребезга контактов, а если речь идет об очень качественном устройстве, то, дополнительно, необходимо будет задействовать энергонезависимую память (EEPROM) ПИКа (для энергонезависимого сохранения настроек) или, после включения питания, по умолчанию, производить фиксацию включения (или выключения) режима тонального вызова.
Все это, конечно, сделать можно, но, если разработчик поставлен перед такого рода выбором, нужно, прежде всего, повнимательнее изучить ту аппаратуру, которая формирует сигнал управления для разрабатываемого устройства, может быть в ней уже имеется то, что нужно?
Во многих случаях, в том числе и в нашем, то, что нужно, имеется в наличии и поэтому просто нет смысла заниматься дублированием.
И в самом деле, в Вертексе имеется и кнопка, и светодиодный индикатор ее включения и выключения.
Под это дело в нем имеется и оперативная память клавиатуры (кнопок) типа триггера, и противодребезговое устройство, и система сброса этого триггера в строго определенное состояние при включении питания (к сожалению, EEPROM память микропроцессора р/станции под эту кнопку не задействована, но и без этого "все прекрасно"), причем, для того, чтобы это выяснить, нужно просто внимательно понаблюдать за поведением светодиодного индикатора при нажатиях этой кнопки и при коммутациях питания.
Для того чтобы задействовать весь этот "подарок судьбы" (а по-простому - "халяву"), необходимо, всего-лишь, сделать отвод от того места пайки выводов светодиода, на котором наблюдается наиболее резкий перепад напряжения и, в дальнейшем, "привязать" программу к логике этого перепада.
При этом, никаких преобразователей уровней вводить в схему разрабатываемого устройства не нужно (для согласования уровней), так как Вертекс собран на пятивольтовых микросхемах.
Итак, имеется все необходимое для составления блок - схемы и принципиальной схемы устройства.
Устройство настолько просто, что можно обойтись и без составления блок - схемы, так что, минуя этот этап, переходим к составлению принципиальной схемы.
Так как применяется стандартный кварцевый генератор, то можно использовать стандартную схему подключения кварца.
Для того чтобы не использовать сетевой блок питания, можно запитаться от радиостанции.
5 вольт в ней имеется, но лучше не перегружать 5-вольтовый стабилизатор напряжения р/станции и ввести в принципиальную схему устройства свой 5-вольтовый стабилизатор на м/схеме 142ЕН5А, для которой, в связи с малым энергопотреблением, радиатор не нужен.
Сигнальные цепи, по которым будет осуществляться обмен данными между ПИКом и радиостанцией определены ранее, остается только определиться: какие именно выводы ПИКа задействовать под эти цепи?
Например, назначим следующую "привязку" цепей:
Вывод порта В RB0 будет входом управления. Этот вывод нужно подключить к тому контакту кнопки тангенты, который не соединен с корпусом.
В этом случае, режиму приема будет соответствовать единица, а режиму передачи - ноль.
Вывод RB2 назначаем низкочастотным выходом устройства тонального вызова. Естественно, его нужно подключить к модуляционному входу передатчика.
Так как этот вход имеет высокую чувствительность, то уровень выходного тонального сигнала нужно понизить и, на всякий случай, принять меры "развязки" по постоянному току.
В простейшем случае, это можно сделать, включив в разрыв цепи, соединяющей НЧ выход устройства тонального вызова с модуляционным входом передатчика, последовательную RC - цепочку.
Вывод RB6 назначаем входом управления (вкл/выкл режима тонального вызова).
Этот вывод нужно подключить к тому месту пайки индикаторного светодиода кнопки А радиостанции, на котором наблюдается максимальный перепад напряжения при переключениях этой кнопки.
Для этого нужно произвести замер напряжения, после чего определиться: какому из двух состояний работы светодиода будет соответствовать, например, режим включения устройства тонального вызова?
Определим так (но можно и по-другому): режиму включения устройства тонального вызова будет соответствовать пассивное состояние светодиода (погашен), а режиму выключения - активное (светится), тогда режиму включения устройства тонального вызова будет соответствовать ноль, а выключения - единица (практический результат замеров).
Таким образом, уровни управляющих сигналов четко "привязались" к исполняемым операциям и к ним, в дальнейшем, будет "привязана" и программа.
Для слухового контроля пользователем р/станции процесса выхода в эфир сигнала тонального вызова, можно подключить к выводу RB2 пьезоэлектрический излучатель (например, от телефонного аппарата).
Примечание: выводы портов можно "привязать" к сигнальным цепям в какой угодно комбинации. Можно назначить их и в порте А.
Теперь ничто не мешает составить принципиальную схему.

Принципиальную схему Вы можете скачать: здесь.

Пояснение: номинал R2, естественно, нужно подбирать по наилучшему качеству модуляции. Для р/станций Vertex-2000 он такой, как указано в схеме. Для других типов радиостанций, возможно, его нужно будет подобрать.
Если ПИК не нужно сбрасывать внешним сигналом сброса, а, в данном случае, это так, то между выводом MCLR и цепью +5v подключается R1 номиналом, обычно, от 1 до 10ком.
Номиналы конденсаторов С3 и С4 указаны с запасом, можно сделать их и меньше.
Внешние цепи устройства тонального вызова 1 - 5 подключаются к разъему на задней стенке Вертекса. В нем есть незадействованные контакты, на которые можно вывести сигналы, необходимые для его работы (для выводов RB0 и RB6), а также и цепь +13,8v.
Вывод корпуса и вывод модуляционного входа имеются.
Разъем монтируется прямо на печатной плате устройства и поэтому никаких механических креплений, при подключении его к р/станции, не нужно.

Итак, "болванка" создана.
Теперь, чтобы наполнить ее содержанием, приступаем к созданию программы.
Собственно говоря, "скелет" программы уже сформирован ранее.
Для большей определенности, остается только составить блок - схему программы.
Перед ее составлением нужно полностью определиться с названиями подпрограмм.
Назначаем следующие названия подпрограмм:
Функции подпрограмм START, PAUSE_1, PAUSE_2 были рассмотрены выше.
Циклическую подпрограмму формирования времени выхода сигнала тонального вызова в эфир назовем, например, CYCLE, а подпрограмму ухода в "вечное" кольцо назовем, например, PRD. Можно назвать их по - любому (в пределах дозволенного), это зависит от фантазии программиста.

Блок - схему программы Вы можете скачать: здесь.

Переходим к тексту программы.
Я сделал два варианта текста программы. Они отличаются только комментариями.
Вариант текста программы с названием cus_2 содержит комментарии в формализованном виде, то есть, они написаны на формальном языке команд.
Вариант текста программы с названием cus_1 содержит неформальные комментарии, которые объясняют суть производимых операций.
Совместное использования обеих этих текстов поможет Вам в дальнейшем.

Файл cus_1.asm Вы можете скачать: здесь.

Файл cus_2.asm Вы можете скачать: здесь.

Для того чтобы далее работать с текстами этих программ, создайте проекты cus_1 и cuc_2, а затем скопируйте в них файлы cus_1.asm и cus_2.asm соответственно.
Как это делается - описано в 5-м разделе.
Можете проассемблировать их и убедиться в отсутствии ошибок.
Лучше всего распечатать тексты этих программ и, в дальнейшем, иметь их перед глазами.
Буду считать, что Вы это сделали.
Итак, перед Вами текст программы cus (так я буду называть любой из них), оформленный по всем правилам составления текста программы.
Ранее я упоминал про "правило 12-ти пробелов". В тексте программы cus это правило использовано на практике.
Давайте разбираться:
Текст программы содержит подробные комментарии, которые я специально ввел в ее текст. Уже из них можно многое узнать.
"В реальной жизни" такого рода комментарии - достаточно большая редкость и разработчики программ либо редко когда себя этим утруждают, либо таким способом "напускают туман" чтобы затруднить анализ текста программы другими людьми, либо еще что-то.
Отчасти этим и объясняется, что при наличии большого количества разнообразных текстов программ, большая их часть так и остается "тайной за семью печатями".
Хотя, для опытных программистов, отсутствие комментариев и не является какой-то непреодолимой преградой, но начинающие могут, с непривычки, просто "разбить себе голову об эту стену".
Чтобы этого с Вами не произошло, давайте потрудимся.
Напоминаю Вам снова и снова: "движение" рабочей точки программы никогда не останавливается (за исключением ухода в спящий режим SLEEP).
Это означает, что, если необходимо задержать выполнение последующих команд, то рабочую точку программы обязательно нужно "закольцевать" (задержать) в каком-то секторе команд программы, из которого она должна выйти либо в момент "очистки" некоего счетчика (таймера), либо в момент поступления какого-то внешнего управляющего сигнала, после чего программа исполняется далее.
Любая программа это постоянная вереница такого рода колец, и без глубокого понимания смысла и технологии этих "закольцовок", программированием лучше вообще не заниматься и не тратить зря на это времени.
Анализируемая ниже программа, является прекрасным образцом кольцевой природы программ и, если Вы глубоко прочувствуете работу этой программы, то Вы найдете ответы на многие из "терзающих" Вас вопросов.
"Шапка" программы
 Напоминаю: все, что находится правее точек с запятой MPLAB "не видит" и к программе, в буквальном смысле этого слова, это не имеет отношения, так как программа, опять же, в буквальном смысле этого слова, это то, что находится в трех вертикальных столбцах в левой части текста программы.
Вверху "шапки" программы, обычно, помещается некая общая пояснительная информация о самой программе и об устройстве, которое она обслуживает.
Эта информация может быть изложена в произвольной форме.
То же самое относится и к комментариям.
"Активная" часть "шапки" начинается с директив LIST и __CONFIG.
Первая указывает MPLAB, какой именно файл, из весьма обширной библиотеки файлов, нужно извлечь, чтобы осуществить "привязку" к конкретному типу ПИКа (в нашем случае - PIC16F84A), а при помощи второй директивы устанавливаются значения битов конфигурации (см. распечатку, которая у Вас имеется).
Битов конфигурации всего 5 и поэтому, из пяти разрядов 16-ричного числа (в нашем случае - 03FF5h) активными (в которых можно что-то менять) являются только 2 последних, а первые три всегда пишутся как 03F.
16-ричному числу F5 соответствует бинарное число 1111 0101.
Сверяемся с распечаткой битов конфигурации. Получается: переводим опорный генератор ПИКа в режим стандартного кварцевого генератора (XT), включаем сторожевой таймер WDT, отключаем таймер включения питания PWRT, отключаем бит защиты памяти программ и EEPROM-памяти.
Что касается PWRT, то, лично я, этим таймером не пользуюсь. Он не представляет собой нечто совершенно необходимое и без него, практически всегда, можно обойтись, так что сейчас не нужно забивать им голову.
Далее, необходимо определиться с регистрами специального и общего назначения.
Порядок этого определения не имеет значения: можно, сначала определиться с регистрами общего, а затем, специального назначения или "перетасовать" их как карты.
Так как "бардак" в тексте программы не нужен, то советую Вам придерживаться того порядка, который Вы видите в тексте программы cus.
Итак, при "прописке" регистров специального назначения, необходимо "прописывать" те регистры, которые задействованы при выполнении программы (к содержимому которых программа обращается).
"Прописывать" те регистры, которые не задействуются при выполнении программы, не нужно.
В простейшем случае (если возникают сомнения типа: "А вдруг он (они) потребуется?"), можно просто "прописать" Все регистры специального назначения, а потом, когда программа будет отработана, убрать лишние. Лично я часто поступаю именно так.
В программе cus Вы видите только те регистры специального назначения, которые задействуются при выполнении программы, лишних нет.
"Прописка" происходит при помощи директивы equ с указанием адресов регистров специального назначения в области оперативной памяти программ (см. распечатку).
Обратите внимание, что адрес регистра TrisB (1-й банк) дублирует адрес регистра PortB (нулевой банк). Это то, о чем я уже говорил ранее (кто забыл - вернитесь назад).
Регистры общего назначения прописываются так же, как и регистры специального назначения.
Отличие только в том, что регистрам общего назначения нужно придумать какие-нибудь имена.
Я назвал их Sec, SecH и SecL.
Какие они выполняют функции - понятно из комментариев.
Конечно, возникает законный вопрос: откуда они взялись и, вообще, что это такое?
А взялись они из программы. Становится еще непонятней: а если, например, программа еще не написана, какие регистры общего назначения "прописывать"?
Ответ: никакие.
Вопрос: а откуда взялись Sec, SecH и SecL?
Ответ: из программы.
Не правда-ли: похоже на испорченную пластинку. Давайте выходить из этого замкнутого круга.
С регистрами специального назначения все понятно: у них фиксированные названия и их, по-началу, можно "прописать всем скопом", а потом избавиться от лишних.
С регистрами общего назначения сложнее.
Пока они не будут фигурировать в программе, эти регистры находятся как бы в "полумертвом" состоянии и представляют собой форму без содержания.
Именно команды программы наполняют эту форму содержанием, закладывая в них числа, производя с ними какие-то действия и считывая результаты этих действий.
Вашему вниманию предоставлена готовая программа, и по этой причине вполне ясно, какие регистры общего назначения нужно прописывать, а, анализируя текст программы, становится также ясным и то, зачем они нужны и какие функции выполняют.
А если программист сидит перед "чистым листом бумаги" и только приступает к составлению программы?
В части, касающейся регистров специального и общего назначения, это делается так:
В "шапке" программы "прописываются всем скопом" регистры специального назначения с расчетом последующего (после составления программы) удаления лишних.
Регистры общего назначения не "прописываются", так как "прописывать" нечего.
Лично я делаю или так, или назначаю какие-то стандартные названия, которые, по ходу составления программы, я могу в любой момент изменить, и "прописываю" регистры с этими названиями.
Например, по ходу создания текста программы, возникла необходимость в счетчике.
Ему тут же придумывается название, например, ABC, присваивается адрес (в области оперативной памяти), например, 0Eh и, после этого, в "шапку" программы добавляется строка:
ABC equ 0Eh
Все, этот регистр "прописан" (введен в действие) и с ним можно работать.
Таким же способом "рождаются" и вводятся в действие другие регистры общего назначения, необходимость в которых возникла по ходу составления текста программы.
В конечном итоге, к концу процесса написания текста программы, в "шапке" программы "накапливается" определенное количество регистров общего назначения, к содержимому которых, по ходу выполнения программы, она обращается.
В тексте программы cus таких регистров "накопилось" три. В других программах их может быть другое количество.
Пошли дальше.
В "шапке" программы Вы видите строку F equ 1.
Смысл этой строки заключается в следующем:
Откройте список команд и обратите внимание на байт - ориентированные команды и, в частности, на букву d, которая является составной частью команды.
В зависимости от значения этого параметра, результат выполнения команды может сохраниться либо в аккумуляторе (регистре W), либо в том регистре, с содержимым которого производится действие (регистре F).
Для комфортного восприятия текста программы удобнее, если в качестве параметра d будет указываться не 0 или 1, а название регистра W или F соответственно.
Примечание: с регистром W все понятно, это его название воспринимается однозначно, а вот регистр F физически не существует.
Буква F, в обобщенном виде, обозначает тот регистр, с содержимым которого производится действие, поэтому за этой буквой может "скрываться" любой из задействованных в программе регистров общего назначения.
Например, команду декремента содержимого регистра ABC, с сохранением результата декремента в нем же, можно "изобразить" так:
decf ABC,1
 но гораздо лучше она будет восприниматься как:
decf ABC,F
 Для того чтобы перейти к этому более комфортному восприятию места сохранения результата выполнения команды и необходима строка F equ 1 в "шапке" программы.
Директива equ дает указание MPLAB: если в тексте программы имеется буква F, то ее нужно "воспринять" как единицу.
Такого же рода манипуляцию можно произвести и с нулем, поставив ему в соответствие букву W.
В этом случае, в "шапку" программы нужно добавить W equ 0. При написании программы "с чистого листа", в "шапку" программы помещаются обе этих строки, а, при окончании работы над программой, текст программы просматривается на предмет наличия или отсутствия, например, команд, результат выполнения которых помещается в регистр W.
Если есть хотя бы одна такая команда, то строка W equ 0 оставляется в "шапке" программы, а если их нет, то удаляется.
То же самое относится и к строке F equ 1
В большинстве случаев, требуется оба этих назначения.
В тексте программы cus результаты выполнения команд сохраняются в тех же регистрах, с содержимым которых производятся действия, а регистр W при этом не задействуется.
По этой причине строка F equ 1 оставлена, а строка W equ 0 удалена.
Идем далее.
Строка RP0 equ 5
С учетом сказанного выше, становится понятным, что директива equ дает указание MPLAB: если в тексте программы имеется комбинация символов RP0, то ее нужно воспринять как цифру 5 (5-й бит регистра STATUS - переключение банков).
В этом случае, команда переключения в нулевой банк будет выглядеть не как bcf Status,5 а как bcf Status,RP0 (установить в 0 пятый бит регистра STATUS), а команда переключения в первый банк будет выглядеть не как bsf Status,5 , а как bsf Status,RP0 (установить в 1 пятый бит регистра STATUS).
Таким же образом, при помощи директивы equ, можно заменить номера любых битов регистров специального назначения на названия этих битов (можно даже придумать свои названия, но это ни к чему).
Названия битов запоминаются гораздо легче, чем номера битов, которые, к тому же, и дублируются (регистров много, а битов всего 8), поэтому настоятельно Вам рекомендую оперировать в тексте программы не номерами битов регистров специального назначения, а их названиями. К таким вещам нужно привыкать с самого начала.
Естественно, в "шапке" программы нужно присваивать названия только тем битам регистров специального назначения, к которым обращаются команды.
В программе cus нужен только один такой бит, его (RP0) и "прописываем".
При написании программы "с чистого листа", можно заменять номера битов на их названия по ходу написания программы и тут же их "прописывать" или, изначально, прикинуть, какие биты могут потребоваться, "прописать" их, а затем, по окончании работы над текстом программы, убрать те биты, к которым команды программы не обращаются.
Из сказанного выше можно сделать вывод: ничего плохого не произойдет, если в "шапке" программы будет "прописано" то, что в программе не используется ("лишнее").
А вот об обратном утверждении такого сказать нельзя: если в рабочей части программы будет использовано "что-то", что в "шапке" программы не "прописано", то MPLAB обязательно укажет Вам на это, то есть, при ассемблировании Вы получите сообщение об ошибке.
И еще один вывод, директива equ ставит в соответствие одно другому: название регистра - его адресу, название бита - его номеру, название константы (константе тоже можно присвоить название) - ее значению.
Применение этой директивы позволяет, в дальнейшем, составить удобный для работы и восприятия текст программы.
И в самом деле, зачем, например, каждый раз при обращении к регистру указывать его адрес, когда этот регистр можно один раз "прописать", а затем указывать только его название, а MPLAB автоматически "найдет" его адрес в области оперативной памяти.
И последнее, что нужно указать в "шапке": точку входа в программу и точку входа в прерывание.
О последнем у нас будет отдельный разговор.
Так как в программе cus прерывания не используются, то и точка входа в прерывание не указывается.
Для того чтобы определить точку входа в программу, необходимо, при помощи директивы org, указать, с какого адреса (в счетчике команд PC) нужно начать исполнение программы (org 0 - начать с нулевого адреса), а затем, при помощи команды безусловного перехода goto, перейти на первую команду "стартовой" подпрограммы (goto START).
В нашем случае это первая команда подпрограммы START.
Теперь о синтаксисе.
Правила очень простые: в "шапке" программы указываются названия регистров общего назначения, битов и констант, в удобной для программиста форме.
Например, в "шапке" программы указано название регистра IntCon. Это означает, что в рабочей части программы, при обращении к этому регистру, обязательно должны буквально соблюдаться правила написания этого слова.
Если Вы укажете его название как INTCON или Intcon, то при ассемблировании получите сообщение об ошибке.
Если в "шапке" бит "прописан" как RP0, то именно в таком виде название этого бита должно фигурировать в рабочей части программы.
Если Вы там укажете его название как rP0 или Rp0, то, опять же, получите сообщение об ошибке.
Сказанное выше относится как к названиям рабочих битов, так и к названиям битов флагов.
Примечание: в тексте программы cus биты флагов не "прописаны", так как операций с флагами в этой программе нет.
Исключением являются регистры W и F.
Их названия можно указывать и с большой и с маленькой буквы. Если их названия указаны с большой буквы, MPLAB автоматически "сделает" из нее маленькую. Например, если в "шапке" указана буква F, то в рабочей части программы можно писать f или F, ошибки не будет. В "шапке" программы, адреса и директивы также можно указывать в произвольной форме.
Например, можно написать адрес регистра как 0CH, 0cH, 0ch, 0Ch, а директиву как equ, Equ, EQu, EQU ...
В рабочей части программы названия подпрограмм (меток) должны буквально совпадать с названиями этих же подпрограмм (меток), указываемыми в командах переходов.
Например, имеется подпрограмма START. Если при переходе на эту подпрограмму, в команде перехода указано Start (например, goto Start, а не goto START), то MPLAB выдаст сообщение об ошибке.
Название подпрограмм (меток) программист придумывает сам. Он может, например, придумать название Start_1. Главное - чтобы это название в командах переходов было точно таким же - Start_1.
Рабочая часть программы
 Программа начинается с подпрограммы START, которая, в свою очередь, начинается с подготовительных операций и заканчивается записью констант времени работы таймера в регистры общего назначения.
Так как запись констант времени работы таймера не требует никаких предварительных установок, то эту группу команд можно разместить, например, в начале ПП START или вставить ее между командами подготовительных операций (то, о чем говорилось ранее), результат будет одним и тем же.
В данном случае, я разместил эти группы команд так, как указано в тексте программы.
ПП START начинается с команды clrf IntCon.
Смысл этой команды заключается в следующем: вывод RB0 порта В задействован как вход. Одновременно, вывод PB0 является входом внешнего прерывания (INT).
К тому же, вывод RB6, по которому организуется прерывание по изменению уровня сигнала на входах RB4...7, также задействован как вход.
О прерываниях разговор будет позже, а пока просто примите это к сведению.
Хотя в программе cus и не предусмотрен уход в прерывания, специалисты фирмы Microchip рекомендуют, в этом случае (и вообще, в случаях, когда задействованы выводы ПИКа, участвующие в организации прерываний, и когда, хотя бы один из них, работает на вход) глобально запретить прерывания.
Для этого достаточно установить в 0 седьмой бит регистра IntCon (глобальный запрет прерываний), но, чаще всего, сбрасывается в 0 весь байт регистра IntCon, что Вы и видите в тексте программы.
В простейшем случае, команду clrf IntCon можно всегда вставлять в начало программы, в которой не предусмотрен уход в прерывания (или предусмотрен, но позднее), особо не задумываясь, нужна она или нет, хуже от этого не будет.
Следующая команда: clrwdt (сброс сторожевого таймера WDT).
Так как сторожевой таймер включен (см. биты конфигурации), его, по ходу исполнения программы, нужно периодически сбрасывать.
Сторожевой таймер представляет собой немудреный ждущий RC - одновибратор (ждущий мультивибратор) с перезапуском. Если его периодически не перезапускать (сбрасывать), он закончит формирование импульса и по заднему его фронту произойдет сброс программы, то есть, переход на первую команду ПП START.
Таким образом, если программа, по каким-то причинам, "зависнет", то WDT выведет ее из этого состояния: в этом и заключается основной смысл его применения.
Так вот, рассматриваемая сейчас команда является первой из нескольких таких же команд, "разбросанных" по тексту программы, что вполне объяснимо, так как WDT нужно периодически сбрасывать, чтобы он не закончил формирование импульса.
Если программа работает нормально (не "зависает"), то WDT никогда не закончит формирование своего импульса, так как он будет периодически перезапускаться до истечения расчетного времени формирования импульса.
Ниже по тексту программы, располагается группа из шести команд. Это и есть основные команды подготовительных операций.
Необходимо (см. информацию по принципиальной схеме):
1. Выводы RB0 и RB6 должны работать на вход, а вывод RB2 должен работать на выход.
Следовательно, речь идет о регистре TrisB. Регистр TrisA "со счетов сбрасываем", так как выводы порта А не задействованы и как они будут работать - не имеет значения.
2. Необходимо определиться с подтягивающими резисторами, предделителем, активными фронтами и тактовыми сигналами, то есть c содержимым регистра OptionR.
Оба этих регистра (TrisB и OptionR) расположены в первом банке (см. распечатку области оперативной памяти).
По умолчанию, программа (начиная с первой команды ПП START) начинает исполняться в нулевом банке, следовательно, прежде чем работать с содержимым регистров TrisB и OptionR, необходимо перейти в первый банк, что и делается при помощи первой команды из этой группы команд: bsf Status,RP0 (установить в единицу 5-й бит регистра Status или, проще говоря, установить первый банк).
Обратите внимание: 5-му биту этого регистра в "шапке" программы присвоено название RP0 (то, о чем говорилось ранее) и поэтому в команде можно указать не номер бита, а, более удобное для восприятия текста программы, его название.
Итак, переход в первый банк осуществлен.
Далее все очень просто: устанавливаем нулевой и шестой бит этого регистра в 1 (работа на вход), а остальные - в 0 (работа на выход).
Собственно говоря, в ноль нужно установить только второй бит, но так как выводы RB1,3,4,5,7 не задействованы и, по этой причине, не имеет значения, как они будут работать, установим их "всем скопом" в 0 (а можно и в 1) и дело с концом.
Бинарное число, которое соответствует такой установке (0100 0001) переводим в более компактную форму.
В данном случае, число переведено из бинарной системы исчисления в десятичную (см. имеющуюся у Вас таблицу). Получилось .65 (вспоминайте: точка перед числом - признак десятичного числа).
Можно оставить его в бинарной форме или перевести в 16-ричную (с соблюдением синтаксиса - см. имеющуюся у Вас таблицу), это дело вкуса программиста. Лично я как-то привык к десятичной системе исчисления.
Данное число определяется (назначается) программистом, следовательно оно будет называться тем, что именуется "константой". Запись в регистр TrisB числа .65 происходит в 2 приема, так как за один прием записать в него константу нельзя.
Сначала константа .65 записывается в аккумулятор (регистр W): movlw .65 (записать константу .65 в регистр W), а затем копируется (после копирования, число .65 из регистра W не удаляется, а остается в нем) из регистра W в регистр TrisB: movwf TrisB. Итак, задействованные в принципиальной схеме выводы порта В работают в соответствии с ранее сформулированным требованием.
Переходим к содержимому регистра OptionR.
Сразу "выбраковываем" то, что не нужно:
Так как никакие внешние тактовые сигналы в работе устройства не используются (используются только сигналы управления, которые не являются тактовыми) и ухода в прерывания нет, то значения 4-го, 5-го и 6-го битов регистра OptionR могут быть любыми, то есть, при работе устройства они ни на что не влияют, поэтому, установим их, например, в 0 (а можно и в 1 или "вразнобой").
Теперь работаем с битами, которые на что-то влияют.
Сначала нужно определиться: задействовать или нет предварительный делитель?
Предделитель может быть включен либо перед таймером TMR0, либо перед сторожевым таймером WDT.
Внешних тактовых сигналов у нас нет, значит и незачем включать предделитель перед TMR0.
Остается WDT.
Давайте поподробнее с ним разберемся.
WDT сбрасывается командой clrwdt. Это означает, что, по этой команде, RC - одновибратор WDT начинает формирование одиночного импульса какой-то фиксированной длительности.
Если "дать" этому процессу "благополучно" закончиться, то сформируется импульс длительностью примерно 18 мс.
"Примерно" - потому, что стабильность RC - одновибраторов оставляет желать лучшего, поэтому разработчики ПИКов предлагают ориентироваться на указанное выше время.
Если WDT работает "сам по себе" (без предделителя), то сбрасывать его командой clrwdt необходимо через промежутки времени менее 18 мс., что не всегда бывает удобно (большое количество команд clrwdt и достаточно трудоемкий процесс определения тех мест в программе, куда эти команды нужно "вставить").
Если перед WDT включить предделитель, то это время увеличивается на величину, кратную коэффициенту деления предделителя.
Например, если предделитель включен перед WDT с максимальным коэффициентом деления (128 - см. распечатку), то команды clrwdt нужно располагать в тексте программы через промежутки времени, меньшими чем 18 мс.* 128= 2304 мс. (2,3 сек.), что потребует меньшего количества команд clrwdt и, практически, устраняет случаи работы программы на грани срабатывания WDT.
В случае "зависания" программы, через 2,3 сек. после его начала, WDT сработает.
"Не мудрствуя лукаво", применяем это на практике.
Итак, определились: устанавливаем предделитель с коэффициентом деления 128 перед WDT.
Для этого необходимо установить первые 4 бита (начиная с младшего) регистра OptionR в 1.
Остался один седьмой бит: вкл./выкл. подтягивающих резисторов порта В.
Для выводов порта В, работающих на выход, выходные каскады их защелок имеют свою нагрузку.
По этой причине, даже если подтягивающие резисторы и включены, они автоматически отключаются от выводов порта В, работающих на выход и остаются подключенными к выводам порта В, работающим на вход.
Таким образом, при рассмотрении вопроса о включении или выключении подтягивающих резисторов, влияние этих резисторов на внешние устройства, подключенные к выводам порта В, работающим на выход, можно не учитывать.
А вот для внешних устройств, подключенных к выводам порта В, работающим на вход, это важно.
Если эти внешние устройства имеют свою нагрузку (например, в цепи коллектора или в цепи стока), то подключение подтягивающих резисторов может перегрузить выходные каскады этих внешних устройств или существенно изменить режим их работы.
В этом случае, подтягивающие резисторы включать не нужно.
А вот если выходы внешних устройств, подключенных к выводам порта В, работающим на вход, - с открытым коллектором или открытым стоком ("своя" нагрузка отсутствует), то нужно их включить.
В этом случае, подтягивающие резисторы используются в качестве нагрузок внешних устройств, что очень удобно, так как не нужно вводить в принципиальную схему дополнительных резисторов.
В нашем случае, подтягивающие резисторы включать не нужно, так как выходы внешних устройств, подключенных к выводам RB0 и RB6, имеют "свои" нагрузки.
Следовательно, 7-й бит регистра OptionR нужно установить в 1.
Подводим итог: в качестве константы, которую нужно загрузить в регистр OptionR должно быть число .143 (1000 1111). Можно использовать также и число 8Fh (см. таблицу перевода).
Так как это число является константой, то, опять же, как и в случае с регистром TrisB, "протаскиваем" его через аккумулятор (W).
Получается то, что Вы видите в тексте программы: подтягивающие резисторы порта В выключены и предделитель, с коэффициентом деления 128, включен перед WDT.
Все, в первом банке "все дела сделаны", делать в нем больше нечего. Теперь - назад в основной рабочий банк (нулевой): bcf Status,RP0 (установить нулевой банк, то есть, сбросить 5-й бит регистра Status в ноль).
Идем дальше.
Теперь, в соответствии с блок - схемой программы, необходимо определиться с значением неких констант, которые определяют величину интервала времени выхода сигнала тонального вызова в эфир.
Реально, при составлении программы "с чистого листа", ПП START так и заканчивается командой bcf Status,RP0.
И это естественно, так как пока неизвестно, что из себя будет представлять таймер.
Только после того, как конструкция и принцип работы таймера будут определены, а, следовательно, и "прописаны" задействованные в нем регистры общего назначения, можно вести речь о закладке в эти регистры каких-то констант.
Забегая вперед, скажу (да это и так видно из текста программы) что таймер "собран" на регистрах общего назначения SecH и SecL и, после их "прописки" и конструирования на их основе таймера, 4 команды записи констант времени работы таймера просто - напросто "вставляются" в конец ПП START (что Вы и видите в тексте программы).
Давайте сделаем так: не будем "ставить телегу впереди лошади", а предположим, что программа пишется "с чистого листа" и принцип работы таймера пока не известен.
В этом случае, сразу после команды bcf Status,RP0 переходим к составлению подпрограммы CYCLE, минуя команды операций с константами таймера и расчитывая, что, впоследствии, эти команды будут "вставлены" в конец ПП START (так и происходит при реальном составлении текста программы).
Итак, пишем подпрограмму CYCLE.
Смотрим в блок - схему программы.
Сначала необходимо проверить: управляющий сигнал включения на передачу есть, или его нет?
Ранее мы определились, что сигнал включения на передачу соответствует нулю, а выключения - единице на выводе RB0 (вспоминаем: вывод RB0 настроен на работу на вход).
Следовательно, этот вывод нужно опросить и поставить в зависимость от результата этого опроса сценарии работы программы.
Вот Вам и типичный пример необходимости применения команды ветвления.
Теперь нужно выбрать, какую из команд ветвления применить?
Для этого нужно предельно четко и ясно сформулировать алгоритм производимой логической операции.
Так как исходные данные определены, то этот алгоритм формулируется следующим образом: если нулевой бит регистра PortB (а он соответствует выводу RB0) равен 1 (передача выключена), то тональный сигнал формироваться не должен, и рабочая точка программы должна где-то "закольцеваться" (вспоминаем: выполнение программы не должно останавливаться) до момента появления на выводе RB0 нулевого уровня, а если нулевой бит регистра PortB равен 0 (передача включена), то должно начаться формирование тонального сигнала (программа должна исполняться далее).
Эта формулировка достаточно конкретна, но не совсем.

Лирическое отступление: любой микроконтроллер, также как и любая микроЭВМ, это всего-лишь бездушная "железяка".
И эта "железяка" абсолютно не терпит никакой неопределенности. Если нужно, чтобы она заработала, необходимо избавиться от любой неопределенности и сформулировать задачу однозначно.
В конечном итоге, задача любого программиста сводится к "борьбе" с неопределенностями и к умению однозначно формулировать алгоритмы работы всех составных частей программы.
Особенно ярко это выражается при использовании команд ветвления.
Итак, устраняем неопределенность, которая заключается в том, что, пока что, непонятно: каким образом и куда именно необходимо перейти рабочей точке программы, чтобы там "закольцеваться"?
Естественно, если речь идет о том, что нужно куда-то перейти, то сама собой возникает мысль, что этот переход можно осуществить, используя команду перехода.
Вопрос: какую именно команду перехода использовать?
Анализируем: "закольцовывать" рабочую точку программы в командах, расположенных ниже (по тексту программы) нельзя, так как ниже будут располагаться команды, формирующие сигнал тонального вызова, а, в соответствии с сформулированным выше алгоритмом, при наличии на выходе RB0 единицы, сигнал тонального вызова формироваться не должен.
Остается только одно место - подпрограмма START, следовательно, нужно перейти на нее.
В этом случае, рабочая точка программы будет "крутиться" по кольцу, зона действия которого: от первой команды ПП START и до команды перехода на ПП START.
Теперь остается только выяснить: какой из двух видов переходов применить (безусловный или условный)?
Условный переход, то есть, переход с использованием стека, применять нет смысла хотя бы потому, что для возврата по стеку требуется лишняя команда (команда возврата), да и вообще, смысла задействовать, для выполнения элементарной "закольцовки", стек, нет.
Вполне достаточно команды безусловного перехода goto.
В данном случае (на выводе RB0 - единица), команда goto просто будет "отфутболивать" рабочую точку программы на первую команду ПП START на каждом "витке" кольца до тех пор, пока на выводе RB0 единица не сменится на ноль.
Итак, устраняем последнюю неопределенность: при наличии на выводе RB0 единицы, должен быть осуществлен безусловный переход на ПП START.
В конечном виде, алгоритм проверки: "Управляющий сигнал передачи включен или выключен?" формулируется так: если нулевой бит регистра PortB (а он соответствует выводу RB0) равен 1 (передача выключена), то должен быть осуществлен безусловный переход (команда goto) на ПП START, а если нулевой бит регистра PortB равен 0 (передача включена), то программа должна исполняться далее.
Все, определенность полная, теперь нужно просто подобрать нужную команду ветвления.
Естественно, эта команда должна быть бит - ориентированной, так как осуществляется операция с отдельным битом.
Смотрим в список команд. Таких команд две: btfsc и btfss.
Команда btfss не подходит, так как необходимо будет, сразу после команды ветвления, устанавливать две команды переходов, устанавливать метку и вообще ломать голову, как выбраться из этой "вилки".
А вот btfsc - в самый раз.
Посмотрите в текст (в оба текста) программы cus на первые две команды ПП CYCLE.
Прочитайте комментарии, мне к ним добавить просто нечего. Не правда ли - как просто.
Единственное, что можно добавить: словосочетание "программа исполняется далее" означает, что следующей будет выполняться команда btfss PortB,6
Если мыслить "глобально", то есть не "привязываясь" к данной программе, а, имея ввиду, программы вообще, то на месте этой команды может оказаться любая другая команда.
Прошу обратить внимание на одну особенность команд ветвления, связанную с переходами.
Суть: любая команда ветвления предполагает наличие двух сценариев дальнейшей работы программы.
Если одним из этих сценариев является сценарий типа "программа исполняется далее", то вторым сценарием, в большинстве случаев, является "переход куда-то" (в подпрограмму, на метку), причем, команда, на которую переходит рабочая точка программы по сценарию "программа исполняется далее", должна быть второй снизу (по тексту программы) от команды ветвления, так как только в этом случае она может "исполняться далее".
Первой снизу от команды ветвления должна быть команда перехода.
Только так можно организовать второй сценарий (первый сценарий - "программа исполняется далее").
В случае наличия и на первом, и на втором месте снизу от команды ветвления, команд переходов, сценарий "программа исполняется далее" выполнен быть не может.
В этом случае выполняется либо один, либо второй переход, хотя, фактически, любой из двух сценариев команды ветвления можно рассматривать как сценарий типа "программа исполняется далее", ведь, по факту, движение рабочей точки программы не прекращается, то есть, в любом случае, программа исполняется далее.
Чтобы навести порядок в терминологии (установить "правила игры"), давайте условимся, что словосочетанием "программа исполняется далее" называется сценарий работы программы, не связанный с исполнением команд переходов.
Таким образом, сценарий типа "программа исполняется далее", в подавляющем большинстве случаев, связан с исполнением второй, после команды ветвления, команды.
Идем дальше.
Смотрим в блок - схему программы.
Следующая операция - проверка включения или выключения режима тонального вызова.
Ход рассуждений - точно такой же, как и выше. Разница заключается в следующем:
Работа происходит с битом № 6 регистра PortB.
Уровни управляющего сигнала "привязаны" к другому органу управления.
Переход, с целью ухода рабочей точки программы в "вечное кольцо", осуществляется в подпрограмму PRD.
Для реализации алгоритма проверки, использована команда btfss.
А теперь - потренируйтесь. Постарайтесь самостоятельно понять "где здесь собака порылась" и почему нужно организовывать эту проверку в том виде, в котором она организована в тексте программы (почему не используется команда btfsc?).
Рассмотренные выше 2 проверки, идущие подряд одна за другой, выполняют роль своеобразного "сита".
Результат их совместной работы таков, что, для того, чтобы началось формирование тонального вызова, необходимо одновременное выполнение 2-х условий: режим тонального вызова должен быть включен и р/станция должна быть включена на передачу.
Подобного рода "сита", при составлении программ, применяются сплошь и рядом.
Если нужно обеспечить одновременное выполнение, например, 3-х или 4-х условий, то применяются 3 или 4 проверки, причем, вовсе не обязательно, чтобы они шли друг за другом "без зазоров", в тексте программы они могут быть и разнесены.
О подпрограмме PRD.
Загляните в блок-схему программы. ПП PRD нужна для того, чтобы, после 3-хсекундной выдачи в эфир сигнала тонального вызова, рабочая точка программы либо "закольцевалась" в этой подпрограмме (если передача включена), либо "ушла" на новый виток полного цикла программы (если передача выключена).
Если текст программы составляется "с чистого листа" и программист находится на теперешней стадии ее составления, то, при проверке включения или выключения режима тонального вызова, в команде безусловного перехода goto просто указывается название подпрограммы (PRD), а сама эта подпрограмма составляется позднее, когда "до нее дойдет дело" (что мы и сделаем в дальнейшем).
Таким образом, не трудно заметить, что, при написании текста программы, работа по его составлению начинается с ПП START и происходит последовательно, от начала к концу текста программы, с возможностью пропусков команд (групп команд), работающих с содержимым регистров, которые еще не определены (но будут определены в будущем) и с переходами в подпрограммы, которые еще не созданы (но будут созданы в будущем).
Можно даже пропускать несколько подпрограмм с расчетом работы над ними в будущем.
При этом только нужно быть уверенным, что выполнение этих "отложенных дел" является только "делом техники" и каких-то "глобальных" просчетов не допущено (блок - схема программы капитально продумана).
Переходите к следующему разделу.

Переход на оглавление


Kea_28

8. Пример создания программы (продолжение)

Итак, мы остановились на том, что, в результате описанных выше проверок, состояние выводов RB0 и RB6 анализируется, и, в зависимости от соотношения уровней сигналов на этих выводах, рабочая точка программы либо "закольцовывается" в ПП START, либо "закольцовывается" в ПП PRD (в обоих случаях - уход в "вечное кольцо"), либо она "движется" далее по тексту проограммы (сценарий типа "программа исполняется далее").
Первый случай был описан выше. Это самая простая "закольцовка".
Во втором случае, рабочая точка программы уходит в "вечное кольцо" в ПП PRD, если режим тонального вызова выключен, а передача включена.
После выключения с передачи, рабочая точка программы должна "прыгнуть" в ПП START, где должна уйти в еще одно "вечное кольцо", выход из которого происходит в момент переключения с приема на передачу.
В третьем случае, рабочая точка программы должна продолжить свое "движение" вниз по тексту программы.
Смотрим в блок - схему программы.
Теперь необходимо сформировать один период сигнала тонального вызова, соответствующий частоте 1450 Гц., то есть, сначала нужно сформировать первую половину периода (полупериод), а затем - вторую (форма сигнала - меандр).
В конечном итоге, задача сводится к изменениям уровней сигнала на выводе RB2 и задержке этих уровней на время полупериода частоты 1450 Гц.
Вывод RB2 работает на выход (см. подготовительные операции), а это означает, что к этому выводу подключен выход защелки, которая управляется вторым битом регистра PortB.
Таким образом, устанавливая 2-й бит регистра PortB в 0 или 1 и задерживая момент следующей смены состояния этого бита, можно сформировать нужный нам период.
Зависимость здесь очень простая: если 2-й бит регистра PortB установить в 0, то на выходе защелки, а, следовательно и на выводе RB2 установится 0, а если 1, то установится 1.
Напоминаю: защелка это триггер и, если он установлен управляющим импульсом (командой) в какое-то из 2-х своих состояний, то он будет находиться в нем до поступления следующего управляющего импульса (другой команды, которая отличается от предыдущей).
Таким образом, сформировав на выходе защелки, например, нулевой уровень, в последующем (при формировании интервала времени его фиксации), подтверждать его не нужно, вплоть до смены этого уровня на единичный.
Из этого следует, что, после установки, например, того же нулевого уровня на выходе защелки (на выводе RB2), рабочая точка программы может "скакать" по каким-то другим командам, производя операции, не относящиеся к операциям управления защелками порта В.
Следовательно, после смены уровней на выводе RB0, можно, на определенное время, "закольцевать" рабочую точку программы в подпрограммах задержки и, таким образом, сформировать в них интервалы времени полупериодов.
Этим сейчас и займемся.
Сначала нужно определиться, с какого уровня начинать формирование полупериода: с нулевого или единичного?
В нашем случае, никаких сигналов синхронизации нет, значит, формирование периода можно начать с любого уровня.
Начнем, например, с нулевого.
Для того, чтобы начать формирование нулевого уровня на выводе RB2, необходимо установить 2-й бит регистра PortB в ноль.
Делаем это при помощи бит - ориентированной команды bcf (bcf PortB,2).
Далее Вы видите три NOPа. При составлении программы с "чистото листа", изначально, их нет.
Они являются элементами точной установки ("подгонки") времени отрицательного полупериода и "вставляются" в текст программы после его "прогонки" через секундомер симулятора MPLAB (об этом - позднее).
Теперь нужно "закольцевать" рабочую точку программы в подпрограмме задержки, которую мы ранее назвали PAUSE_1. Конструкция ее должна быть Вам понятна. Она построена по образу и подобию рассмотренной ранее подпрограммы задержки программы Multi.asm.
Следовательно, необходимо, в качестве счетчика, назначить регистр (регистры) общего назначения.
Количество этих регистров зависит от величины времени задержки. Чем большее время задержки нужно обеспечить, тем большее количество регистров требуется.
Расчет их количества и "грубое" определение величины константы:
Период сигнала с частотой 1450 Гц. = 689,6 мкс., а полупериод - 344,8 мкс.
Округляем время полупериода до 345 мкс. Такую величину задержки и нужно сформировать.
Полный цикл рассматриваемой подпрограммы задержки составляет 3 м.ц. (3 мкс.) + 2 м.ц. (2 мкс.) на последнем "витке".
Если применить конструкцию ПП задержки программы Multi.asm, то одним регистром можно обеспечить задержку до 256*3-1=767 мкс.
Полупериод равен 345 мкс., следовательно, достаточно одного регистра общего назначения.
Теперь, если речь идет о задержках, уместно вспомнить о том, что не мешало бы сбросить WDT (вспомните, по ходу выполнения программы, его нужно периодически сбрасывать).
Команду clrwdt можно поставить следующей, после команды bcf PortB,2 c сохранением 3-хмикросекундного полного цикла ПП задержки, а можно и "встроить" команду clrwdt в полный цикл ПП задержки, увеличив полный цикл ПП задержки до 4 мкс (команда clrwdt выполняется за 1 м.ц.).
В обоих случаях, ПП задержки будет работать, но, при формировании ими одинакового времени задержки, естественно, величины их констант не будут равны.
C точки зрения того, что "кашу маслом не испортишь", выгоднее "врезать" команду clrwdt в ПП задержки, чем ставить ее после команды bcf PortB,2, так как, в этом случае, сброс WDT будет происходить чаще (1 раз в 4 мкс., вместо 1 раза за 690 мкс.).
В предлагаемом Вашему вниманию тексте программы cus, Вы видите именно этот вариант построения ПП задержки, который я применил даже не по причине необходимости столь частого сброса WDT, а в обучающих целях.
Дело в том, что полный цикл ПП задержки можно сделать гораздо большим, чем 3 м.ц., "врезая" в нее другие команды, по образу и подобию "врезанной" в нее команды clrwdt.
В этом случае, кроме какого-то полезного действия (сброс WDT), увеличивается полный цикл ПП задержки.
А ведь вместо одной команды можно "врезать" и несколько. С прибавлением каждой такой команды полный цикл ПП задержки будет увеличиваться на время количества циклов выполнения такой "врезанной" команды (большинство команд выполняется за 1 м.ц.).
Если во время выполнения ПП задержки не нужно производить каких-то активных действий, то "встраивается" NOP (или NOPы).
Мало того, таким же образом в ПП задержки можно "встраивать" целые группы команд, подпрограммы, в том числе и циклические и даже группы подпрограмм.
В качестве примера можно привести любую программу частотомера. В них, за время формирования измерительного интервала времени, выполняется несколько подпрограмм иного функционального предназначения.
Все, что касается такого рода "врезок", очень важно и на это нужно обязательно обратить внимание.
Для того чтобы "закрыть" эту тему, приведу практический пример:
Предположим, что используется классическая конструкция ПП задержки (как в программе Multi.asm), обеспечивающая максимальное время задержки 767 мкс., а нужно сформировать задержку в 1000 мкс.
Можно задействовать второй регистр общего назначения, а можно обойтись и одним, "встроив" в ПП задержки всего один NOP.
В этом случае, полный цикл ПП задержки увеличивается с 3-х до 4-х м.ц., и при помощи такой ПП задержки можно сформировать задержки до 256*4-1=1023 м.ц. (1023 мкс.)
Задержка в 1000 мкс. попадает в этот интервал, следовательно, можно обойтись одним регистром общего назначения.
Если в ПП задержки "встраивается" несколько команд, то название ПП задержки должно приходиться на первую команду, из этой группы команд, а после последней команды, из этой группы команд, должна следовать команда ветвления.
Теперь переходим к ПП cus в части, касающейся подпрограммы PAUSE_1.
Делаем "грубую прикидку".
Ранее мы выяснили, что для того, чтобы обеспечить задержку в 345 мкс., достаточно одного регистра общего назначения (даже без "врезки").
Если, при 4-х машинных циклах полного цикла ПП PAUSE_1 (с "врезкой" clrwdt), обеспечивается максимальная задержка в 1023 мкс. (что соответствует максимальному значению константы .255), то значению времени задержки 345 мкс. будет соответствовать значение константы 345*255/1023=85,99...
Округляем до 86-ти.
При написании программы "с чистого листа" это значение (.86) и нужно занести в программу в качестве константы.
В тексте программы Вы видите другое значение константы (.85) и три NOPа.
Такого рода коррекция произойдет позднее, при "отстройке" программы в симуляторе MPLAB (я расскажу об этом подробно), а пока используем результат "грубой прикидки" (.86).
Теперь остается только придумать название этого регистра (назовем его Sec) и "прописать" его в "шапке" программы (назначаем для него в области оперативной памяти адрес 0Ch).
Вот вам и ответ на вопрос о том, откуда взялся в "шапке" программы регистр Sec и каков механизм его "возникновения".
После этого, константа .86 обычным образом (за 2 приема) закладывается в только что "рожденный" регистр Sec.
"Закладку" нужно сделать до первой команды подпрограммы (PAUSE_1 или PAUSE_2), в которой используется этот регистр.
Далее по тексту программы располагаются 3 команды ПП PAUSE_1, комментарии к которым Вы найдете в тексте программы и к которым мне добавить нечего.
Идем дальше.
Теперь необходимо сформировать положительный полупериод.
По своей конструкции ПП PAUSE_2 такая же, как и ПП PAUSE_1, только, перед ее началом, 2-й бит регистра PortB меняет свое состояние с 0 на 1 и внутри ПП задержки PAUSE_2 осуществляется безусловный переход на ту же ПП PAUSE_2.
Регистр Sec уже имеется в наличии, так что ничего "назначать и прописывать" не нужно.
На момент перехода рабочей точки программы на команду bsf PortB,2 , в регистре Sec будет "находиться" число, равное нулю (конечный результат декремента содержимого регистра Sec в ПП PAUSE_1) и, естественно, перед выполнением ПП PAUSE_2, нужно заложить в регистр Sec новую константу, которая будет определять время положительного полупериода.
При написании программы "с чистого листа", в качестве этой константы можно заложить все то же число .86, с расчетом дальнейшей коррекции этого значения при отстройке программы в симуляторе.
NOPов также нет. Все это оставляется "на потом".
Почему в отработанном тексте программы фигурирует число .83, Вы узнаете позже.
Примечание: классическая подпрогамма задержки (такая, как в программе Multi.asm) и она же, но с "врезками" (такая, как в программе cus.asm), по своей сути, есть вычитающий счетчик импульсов, то есть, абсолютно необходимое в цифровой технике устройство.
Восприятию этого факта мешает то, что импульсов, которые нужно считать, как-будто бы и нет, а есть команды, что, на первый взгляд, не одно и то же.
Но, на самом деле, импульсы есть, и считаются именно они, так как результатом исполнения команды (например, такой как decfsz) является активный перепад, формируемый внутри микроконтроллера (то есть, его прохождение мы не имеем возможности проконтролировать при помощи приборов), который и уменьшает (или увеличивает) на единицу содержимое тех регистров общего назначения, которые программист назначает в качестве счетчиков.
В нашем случае, это регистр Sec.
В простейшем случае, можно считать, что каждый раз, при выполнении команды инкремента или декремента, внутри ПИКа, формируется некий "виртуальный" импульс, который, в зависимости от команды (инкремента или декремента), либо увеличивает, либо уменьшает на единицу содержимое созданного счетчика.
При этом следует иметь ввиду, что при работе с микроконтроллерами, счетчик реализуется не одними аппаратными средствами (например, как 555ИЕ2), а комплексом аппаратных и программных средств.
И в самом деле, для того чтобы создать счетчик необходимо не только назначение в качестве счетчика, например, все того же регистра Sec, но и использование при работе с его содержимым той или иной циклической подпрограммы, в состав которой должны обязательно входить байт - ориентированные команды ветвления инкремента или декремента (incfsz и decfsz).
В первом случае получается суммирующий, а во втором случае - вычитающий счетчик.
Практический вывод из этого следующий:
Для создания счетчика, необходимо назначить в качестве счетчика регистр (регистры) общего назначения, определить его начальную установку, предварительно заложив в этот регистр (регистры) константу (константы), создать циклическую подпрограмму с использованием команд incfsz (суммирующий) или decfsz (вычитающий), обращающихся к содержимому назначенного (назначенных) счетчиком регистра.
Все это мы уже проделали.
Примите к сведению: если счетчик, например, духкаскадный (используются 2 регистра общего назначения, то он не обязательно должен быть только суммирующим или только вычитающим, но он может быть еще и комбинированным: один его разряд может работать на суммирование, а другой - на вычитание или наоборот.
Пример конструкции двухкаскадного вычитающего счетчика будет рассмотрен ниже.
И еще одна особенность: при исполнении программы счетчик не может все время считать не останавливаясь.
Если такое произойдет, то программа просто "зависнет".
По этой причине, счетчик должен считать от числа (константы), заложенного в него предварительно (так называемая предустановка) до момента установки нуля (очищения), после чего рабочая точка программы должна выйти из ПП задержки (счета) и программа должна исполняться далее.
Даже в случае ухода рабочей точки программы в "вечное кольцо" (специфическая разновидность счетчика), когда константы не назначаются, она все-равно рано или поздно из него выходит (по внешнему воздействию).
При программировании, понятия "счетчик" и "задержка" - два "сиамских близнеца" и их нельзя отделить друг от друга.
И в самом деле, если речь идет о нулях и единицах, то счетчик, при условии, что он когда-то остановится, всегда обеспечивает задержку (разнесение во времени моментов начала и конца его работы), а задержка какого-то процесса на время кратное машинному циклу, предполагает их подсчет, то есть, применение счетчика.
Таким образом, ПП PAUSE_1 или PAUSE_2 можно описать так: циклическая подпрограмма задержки с "врезкой" из одной команды, на однокаскадном вычитающем счетчике, с предварительной установкой начальной точки отсчета и выходом из полного цикла подпрограммы при очистке счетчика.
Обращаю Ваше внимание на следующее: такого рода общие рассуждения следует рассматривать не как мешающее отвлечение от разбора текста программы, а как необходимый элемент понимания программистом смысла и механизма производимых им действий, без которого его работа подобна езде на машине в условиях тумана.
Попрошу отнестись к такого рода общим рассуждениям со всей серьезностью и, если это так, то, в дальнейшем, у Вас не будет проблем с пониманием текстов следующих программ, так как, при их описании, я буду исходить из того, что Вы усвоили предыдущий материал.
Закончим тему общих рассуждений по "закольцовкам" рабочей точки программы.
На примере того, что уже рассмотрено, можно сделать следующий общий вывод: "закольцовка" рабочей точки программы есть задержка выполнения следующей (после ПП задержки) команды (если она выполняется, то это один сценарий работы программы типа "программа исполняется далее"), посредством многократного перехода на начало исполнения ПП задержки (другой сценарий работы программы типа "задержка"), до очищения регистра (регистров) общего назначения, назначенного в качестве счетчика.
В этом случае, выбор одного из двух сценариев работы программы определяется либо состоянием специально создаваемого под это дело счетчика с предустановкой константы (констант), если время нахождения в "закольцовке" нужно сделать калиброванным, либо заранее определенным уровнем сигнала, формируемого каким-то внешним устройством, если время нахождения в "закольцовке" (в "вечном кольце") нужно "привязать" к сигналу управления, формируемому внешним устройством.
Механизмы работы этих "закольцовок" уже были рассмотрены выше ("закольцовка" с калиброванным временем в ПП PAUSE_1 или _2 и уход в "вечное кольцо" в ПП START или PRD).
Если кто-то до конца не понял правил функционирования этих механизмов, то вернитесь назад и разберитесь с этим.
В идеале (пусть не сейчас, а в будущем), у Вас должно сложиться свое индивидуальное образное восприятие подобного рода процессов, которое очень помогает при составлении текстов программ.
Теперь переходим к тексту программы cus.
Мы остановились на том, что сформировали оба полупериода (то есть, один период) тонального сигнала вызова.
Естественно, этого маловато. Нужно последовательно сформировать несколько тысяч таких периодов и, причем, так, чтобы между ними не было временных "зазоров", а также с учетом заданной скважности (0,5).
Так как время выдачи сигнала тонального вызова в эфир вовсе не обязательно точно "привязывать" к интервалу времени ровно в 3 секунды, то это требование можно сформулировать так: сигнал тонального вызова должен выдаваться в эфир в течении приблизительно 3-х секунд.
Совершенно понятно, что, после окончания формирования одного периода, нужно сразу же начать формирование следующего и т.д. до тех пор, пока не закончится 3-хсекундный интервал времени.
Теперь переходим к устранению всех неопределенностей, иначе ПИК "не поймет", что он должен сделать и "пойдет с Вами на конфликт".
Предположим, что мы работаем "с чистого листа".
На данный момент составления текста программы известно, что, при переходе с приема на передачу (на выводе RB0 1 меняется на 0), должен быть запущен некий счетчик времени (таймер), который отмеряет 3 секунды.
По окончании формирования этого интервала времени: если передача включена, то рабочая точка программы должна уйти в "вечное кольцо" в подпрограмме PRD (ее еще нет, мы просто дали ей название, а что там в ней - "тайна покрытая мраком") и выйти из него при переключении с передачи на прием (перейти на новый полный цикл программы), а, если передача выключена, то рабочая точка программы сразу же должна уйти в "вечное кольцо" в подпрограмме START и выйти из него при переключении с приема на передачу (см. блок - схему программы).
Детализируем:
Так как, сначала, нужно сформировать 3-хсекундный интервал времени выдачи сигнала тонального вызова в эфир, а только после этого производить уход в "вечное кольцо", то группа команд, производящих операции с таймером должна быть расположена в тексте программы сразу же после последней команды ПП PAUSE_2, а группа команд ПП PRD должна следовать сразу же после последней команды группы команд, производящих операции с таймером.
Теперь порядок следования ясен и можно перейти к конструированию таймера.
Для тех, кто хорошо усвоил предыдущий материал, должно быть совершенно понятным, что, если речь идет о формировании интервала времени (3 сек.), то необходимо создание счетчика, который бы подсчитывал количество, сформированных предшествующими подпрограммами, периодов (он же - то, что мы назвали таймером).
Так как необходимо сформировать фиксированный интервал времени и после этого уйти в сценарий "программа исполняется далее" (перейти на ПП PRD), то, очевидно, что счетчик можно построить по образу и подобию рассмотренного нами выше классического счетчика (см. ПП PAUSE_1 и PAUSE_2).
В этом случае все очень просто: все 3 команды ПП, например, PAUSE_1 (вместе с названием ПП) просто вставляются (копируются) в текст программы сразу же после команды goto PAUSE_2.
Естественно, теперь нужно "навести порядок".
Название ПП (PAUSE_1) необходимо либо заменить, так как текст программы не должен содержать две ПП с одинаковым названием, либо изъять из текста программы.
Кроме того, нужно определиться: на какую ПП (или метку) необходимо осуществить безусловный переход (goto)?
Считать необходимо количество периодов, следовательно, при "закольцовке" счетчика, формально, нужно перейти на ту команду, с которой начинается формирование периода, то есть, на команду bcf PortB,2
Посмотрите в текст программы cus.
Эта команда не является первой командой подпрограммы, и она ничем не помечена (метка не установлена).
Для того чтобы на нее перейти, необходимо придумать какое-нибудь название для метки и пометить ей данную команду, например, Metka_1 bcf PortB,2 и осуществить безусловный переход на метку (goto Metka_1).
Это, конечно, сделать можно, но есть более выгодный вариант такого перехода - переход на ПП CYCLE.
В этом случае, задействуются команды обеих проверок, которые рассматривались выше.
Этот вариант выгоден тем, что, если выключение с передачи происходит во время формирования сигнала тонального вызова (во время 3-хсекундного интервала времени), то, в момент перехода с передачи на прием, формирование сигнала тонального вызова тут же прекращается за счет "закольцовки" рабочей точки программы (уход в "вечное кольцо") в ПП START ("зона" кольца: от 1-й команды ПП START до команды goto START при проверке наличия включения на передачу) и, к тому же, метку выставлять не нужно.
В случае перехода по метке на команду bcf PortB,2, 3-хсекундный интервал сформируется полностью и, только после этого, рабочая точка программы уйдет в "вечное кольцо" ПП START.
Не смотря на то, что программа cus полностью не отработана (мы так предположили), последнее утверждение истинно по той причине, что данный полный цикл программы (в котором, во время формирования 3-хсекундного интервала времени, произошло переключение с передачи на прием) когда-нибудь закончится и произойдет переход на новый полный цикл программы (то есть, на ПП START), а, следовательно, проверка наличия включения на передачу неизбежна, со всеми вытекающими, в этом случае, последствиями (уход в "вечное кольцо").
С практической точки зрения, вариант с переходом на метку не удобен тем, что, в указанном выше случае, пьезоэлектрический излучатель будет выдавать тональный сигнал тогда, когда, по факту, его выхода в эфир не происходит, что не совсем удобно для пользователя.
Итак, мы определились: осуществляем безусловный переход в ПП CYCLE (заменяем команду goto PAUSE_1 на команду goto CYCLE).
"Старое" название подпрограммы (PAUSE_1) из первой колонки нужно убрать и оставить "это место пустым", так как безусловный переход будет осуществлен на 1-ю команду ПП CYCLE.
Таким образом, все команды программы, от 1-й командой ПП CYCLE и до команды decfsz SecL,F, можно считать "врезкой" в кольцо циклической ПП задержки CYCLE, которая "проходится" рабочей точкой программы за время, равное одному периоду сигнала тонального вызова.
"Старую" "врезку" (clrwdt) можно удалить, так как WDT, при формировании периода, и так сбрасывается часто.
Необходимо сформировать, приблизительно, 3-хсекундный интервал времени.
Период тонального сигнала равен 690 мкс. (см. выше).
Таким образом, необходим счетчик, считающий, как минимум, до 3000000/690=4347,826 (округляем) =4348.
"Грубая прикидка": при применении стандартной ПП задержки (полный цикл ПП = 3 м.ц.), значение константы, закладываемой в назначаемый в качестве счетчика, регистр общего назначения, должно быть 4348/3= приблизительно 1450.
Максимальное значение константы, которую можно заложить в один регистр, составляет .255 На одном регистре общего назначения, при использовании хитроумной "врезки" типа дополнительной ПП задержки, собрать такой счетчик, конечно, можно, но это не самый лучший выход из положения.
Гораздо удобнее и проще применить двухразрядный счетчик.
Такой счетчик считает до 256*256=65536 и, с его помощью, можно сформировать (применительно к нашему случаю) не только 3-хсекундный интервал времени, но и гораздо больший.
Если речь идет о нескольких разрядах, то нужно определить порядок их старшинства.
Старший разряд пометим буквой H, а младший - L.
Примечание: такого рода пометки - стандарт, хотя, можно "привязаться" и к другому порядку этих букв или вообще придумать что-нибудь свое.
Если счетчик 3-хразрядный, то добавляется буква M - средний разряд (порядок старшинства: H, M, L), а если 4-хразрядный, то добавляются буквы HH - "старший старшего" (порядок старшинства: HH, H, M, L).
"Прописываем" в "шапке" программы регистры общего назначения, назначенные в качестве разрядов счетчика: называем их, например, SecH (старший разряд) и SecL (младший разряд) и назначаем им адреса в области оперативной памяти, например: 0Dh и 0Eh соответственно.
Вот Вам и ответ на вопрос: откуда взялись в "шапке" программы регистры SecH и SecL?
В части, касающейся рассматриваемой группы команд, получилось то, что Вы видите в тексте программы cus. Пока что Вам может быть не совсем понятна конструкция 2-хразрядного счетчика, так что будем разбираться: что это такое и откуда что взялось?
Давайте организуем работу 2-хразрядного вычитающего счетчика по стандартному принципу: сначала декрементируется содержимое счетчика младшего разряда, а, после его очищения (в счетчике - ноль), декрементируется содержимое счетчика старшего разряда.
Декремент счетчика младшего разряда будет происходить каждый раз после формирования периода тонального сигнала, а декремент счетчика старшего разряда будет происходить через каждые 256 периодов тонального сигнала, в момент смены, в младшем разряде счетчика, числа .255 на число .0 Сигналом окончания формирования 3-хсекундного интервала времени является очищение счетчика старшего разряда, после чего программа должна перейти на сценарий: "программа исполняется далее".
Другой сценарий (циклический), собственно говоря, и есть сам процесс счета.
По своей сути, этот механизм ничем не отличается от механизма работы 2-хразрядного счетчика, собранного, например, на микросхемах счетчиков (...ИЕ...) и тот, кто с ними работал, без особого труда поймет, о чем идет речь.
В регистры SecH и SecL, естественно, нужно закладывать какие-то константы.
Определяемся с ними.
Для того чтобы отмерить 3 сек., 2-хразрядный счетчик должен посчитать 4348 периодов (см. выше).
Счетчик младшего разряда декрементируется каждый период.
Если установить в нем константу, например, .255, то, в процессе последовательно "идущих" друг за другом декрементов, в момент установки того же самого числа .255 (прошел один полный "виток" работы счетчика) будет подсчитано 256 периодов и произойдет декремент содержимого счетчика старшего разряда.
Таким образом, при прохождении 4348 периодов, счетчик младшего разряда должен совершить 4348/256=примерно 17 "витков" и, соответственно, должно произойти 17 декрементов содержимого счетчика старшего разряда.
Таким образом, для того чтобы очистить счетчик старшего разряда примерно за 3 сек., необходимо заложить в него константу .17 В счетчик младшего разряда закладывается максимально возможное значение константы (.255), которое, по этой причине, можно приравнять единице значения константы, закладываемой в счетчик старшего разряда.
Таким образом, в счетчик старшего разряда необходимо заложить "прикидочную" константу .17 - .1 = .16
Еще раз обращаю Ваше внимание на следующее:
Между первой командой ПП CYCLE и командой decfsz SecL,F располагаются не только отдельные команды, но и целых две циклические подпрограммы.
Подпрограмму CYCLE следует классифицировать как циклическую подпрограмму задержки с "массивной врезкой", включающей в себя две циклические ПП задержки PAUSE_1 и PAUSE_2.
Это - наглядная иллюстрация того, о чем я говорил ранее (в "врезку" могут входить не только отдельные команды, но и целые циклические подпрограммы).
Вывод: в циклическую ПП задержки, образно выражаясь, "врезать" можно "все что угодно". Главное при этом то, чтобы эта "врезка" была "жизнеспособной" (работала по заданному алгоритму и не "висла"), и чтобы ее конструкция обеспечивала требуемый полный цикл ПП задержки.
Если говорить конкретно о циклической ПП задержки CYCLE, то, с учетом сказанного выше, можно сделать следующий вывод: "врезка" подпрограммы CYCLE работает по заданному разработчиком алгоритму и обеспечивает полный цикл этой подпрограммы, равный периоду сигнала с частотой 1450 Гц. (690 мкс.).
2-хразрядный счетчик отсчитывает число периодов, "помещающихся" в 3-хсекундном интервале времени и, по его окончанию, осуществляется переход на сценарий "программа исполняется далее".
"Для полного счастья" остается только окончательно разобраться с константами.
Чтобы долго не мудрствовать, "привяжемся" к тем значениям констант, которые были определены выше, то есть: .255 - в младшем разряде счетчика и .16 - в старшем.
С учетом того, что "прикидка" производилась "грубо" и, в результате этого, реальное время выхода в эфир сигнала тонального вызова окажется несколько большим расчетного, уменьшим величину константы счетчика старшего разряда на единицу и сделаем ее равной .15, то есть, сигнал тонального вызова, в этом случае, будет выдаваться в эфир в течении времени чуть меньшего, чем 3 сек.
На фоне слова "приблизительно", такого рода изменения - вещь субьективная. Лично я решил так, а другой программист может принять другое решение, например, уменьшить значение константы счетчика младшего разряда, а значение константы счетчика старшего разряда не менять или все перерасчитать и перейти к другому соотношению числовых значений констант или, вообще, перейти к другому значению времени выхода в эфир сигнала тонального вызова.
Если, как в нашем случае, не требуется "привязки" формируемого интервала времени к строго конкретному значению, достаточно только приблизительного расчета, подобного приведенному выше, после проведения которого, расчетные константы можно сразу и окончательно закладывать в программу и, в дальнейшем, не тратить время на проверку значений этих интервалов времени в симуляторе.
Естественно, в этом случае, необходимо четко представлять, как работает программа.
Если, при формировании какого-то интервала времени, возникают сомнения по поводу величин назначенных констант, то устранить их можно при отладке программы в симуляторе, он для этого и существует.
Если речь идет о формировании калиброванных интервалов времени (в нашем случае - времени полупериодов), то, в большинстве случаев, без проверки их значений в симуляторе и соответствующей коррекции констант, не обойтись.
Итак, "окончательно и бесповоротно" назначаем: константа старшего разряда счетчика - .15, константа младшего разряда счетчика - .255 Теперь возникает вопрос: в каком месте текста программы их "заложить"?
Ответ на этот вопрос был дан ранее, поэтому помещаем команды закладки констант в регистры SecH и SecL в конце ПП START.
Это делается аналогично закладке константы в регистр Sec, только, вместо одного, константы закладываются в 2 регистра.
Вот Вам и ответ на вопрос: откуда взялись в конце ПП START 4 команды записи констант и почему их содержание именно такое?
Общий принцип: если закладка нескольких констант в несколько регистров (в пределах N-разрядного счетчика) происходит последовательно одна за другой, то порядок закладки не имеет значения.
Применительно к нашему случаю, это означает, что можно сначала заложить константу в регистр SecH, а потом - в регистр SecL, а можно и наоборот.
Подвожу краткий итог того, что сделано (в упрощенной форме): Декремент 2-хразрядного счетчика производится при наличии на НЧ выходе устройства единичного уровня, так как рабочая точка "заходит в счетчик" во время формирования положительного полупериода.
Во время формирования 3-хсекундного интервала времени, после перехода рабочей точки программы в ПП CYCLE, установка нулевого уровня на НЧ выходе устройства происходит при выполнении первой команды группы команд формирования отрицательного полупериода (bcf PortB,2), после чего отмеряется время отрицательного полупериода
Далее, при выполнении первой команды группы команд формирования положительного полупериода (bsf PortB,2), на НЧ выходе устройства устанавливается единичный уровень, отмеряется время положительного полупериода и рабочая точка программы через 1-й или 2-й разряд счетчика опять "уходит" в ПП CYCLE и все повторяется снова и снова, пока, с момента включения на передачу, не пройдет 3 сек.
Посмотрите в текст программы cus.
После 4-х команд 2-хразрядного счетчика Вы видите команду bcf PortB,2 Именно на эту команду переходит рабочая точка программы после того, как счетчик старшего разряда очистится, то есть, завершится формирование 3-хсекундного интервала времени выхода тонального сигнала в эфир (переход на сценарий "программа исполняется далее").
Этой командой осуществляется смена единичного уровня на НЧ выходе устройства на нулевой для того, чтобы, после очищения счетчика старшего разряда, закончить формирование положительного полупериода.
Эта команда вставлена в текст программы "для порядка" и, по большому счету, без нее можно обойтись (вычеркнуть).
И в самом деле, какая разница, какой уровень зафиксировать на НЧ выходе устройства после формирования 3-хсекундного интервала времени выхода тонального сигнала в эфир (вспомните про разделительный конденсатор)?
Можно эту команду оставить, а можно и удалить, только, в последнем случае, нужно иметь ввиду, что рабочая точка программы, после того, как счетчик старшего разряда очистится, перейдет на первую команду ПП PRD, которую сейчас предстоит создать.
Составляем подпрограмму PRD.
Напоминаю, что она должна делать.
В этой ПП, рабочая точка программы должна уйти в "вечное кольцо" и выйти из него в момент переключения с передачи на прием, с последующим переходом на новый цикл программы.
Механизм функционирования "вечного кольца" рассмотрен ранее (уход в "вечное кольцо" ПП START).
В ПП PRD он точно такой же.
Разница - в деталях: так как "закольцовка" происходит внутри ПП PRD, то команда безусловного перехода должна обращаться к ПП PRD, и при опросе нулевого бита регистра PortB используется команда btfss вместо btfsc (если применить btfsc, то будет "вилка").
Если речь идет о длительной "закольцовке" (что и имеет место быть), обязательно необходимо сбрасывать WDT не реже одного раза за 2,3 сек. (в нашем случае).
В "вечном кольце" ПП START это делалось командой clrwdt, расположенной в группе команд подготовительных операций.
Также нужно поступить и по отношению к ПП PRD, то есть, "врезать" команду clrwdt в полный цикл "вечного кольца" этой ПП.
Для такой простой ПП задержки, не имеющей "массивных врезок" (в отличии от конструкции "вечного кольца" ПП START), команду clrwdt можно "пристроить" единственно разумным способом: сверху от команды ветвления с переносом названия ПП задержки на команду clrwdt.
Посмотрите, как это выглядит в тексте программы cus.
Необходимость применения команды clrwdt обусловлена тем, что время включения на передачу может быть большим, чем 2,3 сек..
Первые 3 команды ПП PRD можно классифицировать так: циклическая ПП задержки с уходом "вечное кольцо", с выходом из него по внешнему управляющему сигналу и "врезкой" из одной команды.
Полный цикл такой ПП для сценария "закольцовка" - стандартный: 3 м.ц.+1 м.ц. команды clrwdt = 4 м.ц., так что срабатывания WDT опасаться не нужно.
По принципу "кашу маслом не испортишь", можно организовать еще одну такую же проверку (хоть десяток), как это сделано в тексте ПП PRD, а можно обойтись и одной.
Итак, мы дошли до конца полного цикла программы.
Остается только вставить в текст программы команду безусловного перехода goto START для перехода на новый полный цикл программы, чем обеспечивается соблюдение "глобального" принципа: рабочая точка программы всегда должна находиться в движении.
Текст программы всегда заканчивается директивой end для того, чтобы MPLAB "знал", что ниже этой директивы ничего "искать" не нужно.
Замечание по WDT: естественно, длительность импульса, вырабатываемого WDT (в нашем случае, - 2,3 сек.), можно уменьшить за счет уменьшения коэффициента деления предделителя.
Можно отключить предделитель от входа WDT (18-ти мс. достаточно) и даже вообще отключить WDT (в битах конфигурации).
Если имеется такое желание, нужно соответствующим образом изменить значение константы, закладываемой в регистр OptionR.

А теперь самое время привести в порядок "растрепанные мысли".
Предполагаю, что после восприятия изложенной выше информации, возможно возникновение "неразберихи" в терминологии, связанной с такими понятиями как: подпрограмма задержки, счетчик, уход в "вечное" кольцо и т.д.
Все это, в своей основе, - одно и то же, хотя, казалось бы, речь идет о выполнении разных функций.
И в самом деле, что общего между, например, ПП PAUSE_1, ПП PRD и ПП CYCLE, кроме того, что все они - циклические?
Конструкции - разные, выполняемые функции - также разные.
Однако, в основе всех их, лежат такие понятия как "задержка" и "счетчик" (вспомните про "сиамских близнецов").
Например, ПП ухода в "вечное кольцо" PRD, с точки зрения удобства восприятия ее функционального предназначения, целесообразно классифицировать как подпрограмму задержки, но ее можно также считать и счетчиком, который считает время от момента очистки старшего разряда 2-хразрядного счетчика и до момента перехода с передачи на прием, что не совсем удобно для восприятия функционального предназначения подпрограммы, но также "имеет право на жизнь".
То же самое можно сказать и о любых других циклических подпрограммах.
Такого рода классификация - процесс субъективный и поэтому, одну и ту же ПП можно классифицировать по-разному, все зависит от того, какое из двух этих восприятий наиболее удобно для программиста (кто-то видит счетчик, а кто-то видит задержку).
Обычно, при такого рода классификации, программист "привязывается" к наиболее "бросающемуся в глаза" (с его точки зрения) функциональному предназначению подпрограммы (группы команд), которое может быть понимаемо другими программистами по-другому.
В последнем случае, возникающие при этом "разброд и шатания", сильно затрудняют обучение программированию.
Конечно, если человек - "упертый", то он, через "пинки, синяки и шишки", рано или поздно вырабатывает в себе умение смотреть на любую ПП или группу команд с двух точек зрения (счетчик/задержка), но лучше изначально разобраться с подобными вещами и не жалеть на это времени.
В качестве тренировки, попробуйте разглядеть в любой из циклических подпрограмм программы cus, одновременно, и устройство типа счетчика, и устройство, осуществляющее задержку.
Очень советую Вам проделать это и с другими циклическими подпрограммами, с которыми мы будем работать.
Дополнение:
Какую именно группу команд считать подпрограммой - определяет программист.
Даже при использовании стандартных ПП, программист может видоизменить ее так, что и "мама родная не узнает" вплоть до того, что лишить ее этого "статуса" и включить ее в состав другой ПП.
Короче говоря, "демократия" - полная.
Однако, существуют определенные правила, которых нужно придерживаться с точки зрения "наведения элементарного порядка".
Если существует группа команд, которые реализуют функционально законченное устройство, то, прежде чем "присваивать ей статус" подпрограммы, необходимо убедиться: предполагается ли в будущем тексте программы хотя бы один переход к первой, из этой группы, команде.
Если такой переход необходим, то этой группе команд можно присвоить "статус" подпрограммы, а если нет, то этого делать не стоит, так как нет смысла "захламлять" текст программы неработающими названиями подпрограмм, в которые нет переходов.
Пример:
2-х разрядный счетчик, реализованный в программе cus, является функционально законченным устройством и, при необходимости "задействования" его в более сложной, чем cus, программе, ему вполне можно было бы "присвоить статус" подпрограммы.
Если бы это было так, то нужно было бы назначить название подпрограммы (например, XYZ) и вставить его в 1-ю колонку (XYZ decfsz SecL,F).
Но, в программе cus, такой необходимости нет и, поэтому, неработающее название ПП (в тексте программы отсутствует переход на эту ПП) вставлять в текст программы нет смысла.
Что касается меток, то они применяются для того, чтобы перейти на любую другую команду, не являющуюся первой командой подпрограммы, то есть, при помощи меток можно "вклиниваться" куда угодно (естественно, в пределах рабочей части текста программы). Пример манипуляций с меткой, в программе cus, был приведен выше.

Краткий общий итог:
Выше описан процесс конструирования конкретного устройства на основе ПИКа.
Надеюсь, что после знакомства с этой информацией, Вы поняли, что, сам по себе, процесс составления программы, если заранее определены исходные данные, не есть что-то суперсложное.
По-началу, с непривычки, некоторые из Вас могут с этим не согласиться, но после того, как Вы "набьете руку", Вы сами удивитесь - как это, в сущности, просто.
Добрый совет: на данной стадии обучения не стоит "стрелять из рогатки по медведю", то есть, не нужно пытаться сразу же сконструировать что-то свое. Для этого, как минимум, нужна "рогатина", а еще лучше - "ружье".
Советую Вам обратить все свое внимание на "разбор полетов", а именно, на анализ текстов "чужих" программ, то есть, программ для устройств с готовыми исходными данными (неопределенности отсутствуют), для приобретения "солидного инструментария" и его "шлифовки".
В этом вопросе я, по мере своих возможностей, в дальнейшем, постараюсь Вам помочь в том смысле, что, кроме анализа текстов рограмм, я постараюсь показать: как реально происходит эта самая "борьба с неопределенностями", вразумительное описание чего, кстати, является, по многим причинам, очень большой редкостью.
В конечном итоге, когда Вы займетесь конструированием, "центр сложности" несколько сместится из "сектора работы по составлению текста программ" в "сектор работы по борьбе с неопределенностями", что предполагает как продуманность принципа работы конструируемого устройства, так и определенность исходных данных для составления программы под это устройство.
До этого, конечно, еще далековато и главная задача сейчас - "прочувствовать" (в том числе, и на уровне подсознания, что, кстати, немаловажно) смысл предварительных установок, динамику движения рабочей точки программы по ее тексту и что при этом может происходить.
До этого момента, работа над текстом программы не так уж сильно и наглядно задействовала механизм образного восприятия работы программы (не все в этом деле определяется только прямолинейной логикой).
Теперь пора его "включить на полную мощность".
Речь идет о работе в симуляторе.
Кроме того, что Вы реально увидите, как рабочая точка программы совершает свое движение (как исполняется программа и что при этом происходит в "недрах" ПИКа), Вы узнаете, что такое, в своем принципе, отладка программы.
Переходите к следующему разделу.


Kea_29

9. Работа в симуляторе. Отладка программы.

О том, что такое симулятор, говорилось ранее. Подошло время привести этот мощнейший "инструмент" в "полную боевую готовность".
Введение
 Для создания общего представления о том, зачем он нужен, достаточно представить себе разницу, например, между счетами и компьютером.
Естественно, что бухгалтер, работающий на компьютере, сделает одну и ту же работу быстрее и качественнее, чем, если бы он работал со счетами.
Не может также вызвать сомнение и то, что, прежде чем быстро и качественно сделать работу, необходимо, сначала, хотя бы, знать "с какого бока подойти" к этому самому компьютеру, на что нажимать и что после этого будет происходить.
Первичное представление о работе в симуляторе можно составить, усвоив соответствующую информацию из раздела, посвященного MPLAB.
Составляя данный раздел, я буду предполагать, что эта информация Вами усвоена.
Руководствуясь принципом "от простого к сложному", сначала рассмотрим случай: программа, с которой работает симулятор, заведомо работоспособна, то есть, заданный разработчиком алгоритм ее работы (в соответствии с блок - схемой программы) безошибочно выполняется.
Обращаю Ваше внимание на то, что, безошибочно выполняемый алгоритм работы программы, означает только то, что выполнение программы происходит в строгом соответствии с блок - схемой программы и все ее "квадратики" реализованы в тексте программы без ошибок функционального характера, но не то, что эта программа идеальна в части, касающейся точного соблюдения заданных разработчиком временных характеристик устройства, обслуживаемого этой программой.
Например, имеется вполне работоспособный частотомер, но, из-за того, что в тексте программы неверно подобраны значения констант, определяющих измерительный интервал времени, он будет "врать".
Можно "превратить" его в "полноценный" измерительный прибор, корректируя значение частоты опорного генератора при условии, что он "врет не сильно".
А если он "врет сильно" и указанная выше коррекция неэффективна?
А если при переходе с одного интервала времени измерения на другой существует большая разница в показаниях?
В этих случаях, нужно "лезть" в текст программы и корректировать значения времязадающих констант или "подгонять" время исполнения одного сценария работы программы под время исполнения другого сценария.
Сделаем это, пока, теоретически, без "привязки" к какой-то конкретной программе, и в общем виде.
Итак, запускаем MPLAB и открываем проект с программой "врущего" частотомера.
Допустим, перед нами - текст программы, обсуживающей частотомер.
Естественно, сначала нужно найти в тексте программы команды закладки соответствующих времязадающих констант в регистры общего назначения, созданные и "прописанные" в "шапке" программы под это дело.
Эти команды - стандартные (о них говорилось выше) и обнаружить их не составляет большого труда (пары команд: movlw, movwf).
Если имеются несколько функционально различных групп (или отдельных) констант, то необходимо посмотреть, в каких устройствах они работают и, анализируя текст программы, "отсеять" ненужные.
Кстати, то, что Вы сейчас читаете - весьма примитивная иллюстрация работы хакера или того, что называют "взломом" и, не смотря на "страшные слова" - вещь исключительно полезная с точки зрения "тренировки мозгов".
Естественно, такого рода манипуляции предполагают определенный уровень профессионализма и этим я просто хочу показать, что, даже при отсутствии комментариев к тексту "чужой" программы, в ней можно "вычислить" все, что угодно и что-то в ней изменить, "перекроить так, что мама родная не узнает" или, на ее основе, создать нечто новое.
Именно к этому нужно всячески стремиться, и именно это является одним из признаков классного программиста, который объективно является хакером, "взломщиком" (или там еще каким-то "нехорошим человеком") уже по определению.
Предположим, место закладки констант в тексте программы обнаружено, и их значения известны.
Если константа всего одна, то - нет вопросов, работаем только с ней.
А как быть, если их, например, 3 или 4 (кстати, программы под реальные частотомеры как раз и содержат примерно такое же количество констант, определяющих величину измерительного интервала времени)?
В этом случае, определяется порядок старшинства.
Собственно говоря, и определять нечего: порядок старшинства, в подавляющем большинстве текстов программ, - стандартный, просто посмотрите на последние буквы названий регистров общего назначения, в которые закладываются константы (вспоминайте: HH, H, M, L).
Одиночная константа, в подавляющем большинстве случаев, закладывается в регистр общего назначения, название которого этих букв, в его конце, не содержит (посмотрите в текст программы cus и найдите этому подтверждение).
Зная порядок старшинства, и оценивая величину погрешности в показаниях частотомера, можно определиться: в каком (каких) из этих разрядов следует произвести коррекцию константы (констант).
Это, в первую очередь, зависит от величины погрешностей в показаниях частотомера.
Если она маленькая, то можно откорректировать значение константы в одном младшем разряде, а если большая, то придется корректировать и значения констант более старших разрядов.
Сам механизм такой коррекции очень прост и действия по замене чисел констант точно такие же, как и при правке текста в любом текстовом редакторе.
Возникает вопрос: в какую сторону производить изменения, в сторону увеличения или уменьшения?
Предположим, что частотомер показывает значение частоты меньшей, чем истинное и "врет" не сильно.
Следовательно, в первую очередь, будем изменять значение константы младшего разряда.
В какую сторону?
Прежде чем ответить на этот вопрос, нужно кое с чем разобраться.
Предположим, что обнаружены места закладки констант трех разрядов (H, M, L).
Запоминаем названия регистров общего назначения, в которые записываются эти константы и ищем в тексте программы команды, которые обращаются к содержимому этих регистров и которые входят в группу команд, при помощи которой реализуется некое устройство.
Те, кто внимательно изучил предыдущие разделы, смогут однозначно ответить на этот вопрос: это "некое" устройство удобнее всего классифицировать как 3-хразрядный счетчик.
И в самом деле, если речь идет о разрядности, а с числами констант нужно производить какие-то операции (они под это дело и создаются), то сам собой напрашивается вывод о том, что при проведении этих операций с содержимым регистров общего назначения, с заложенными в них константами, должны использоваться байт - ориентированные команды ветвления инкремента incfsz (разряд суммирующего счетчика) или/и декремента decfsz (разряд вычитающего счетчика).
В данном случае (необходимо увеличить интервал времени измерения), необходимо посмотреть, какая именно из этих двух команд обращается к содержимому регистра общего назначения с заложенной в него константой младшего разряда (L).
Если это команда decfsz (в большинстве случаев), то величину константы младшего разряда счетчика нужно увеличивать, а если incfsz, то уменьшать.
Если диапазона изменения значения константы счетчика младшего разряда не достаточно чтобы устранить погрешность показаний частотомера, то, по такому же принципу, необходимо изменить значение константы счетчика среднего разряда (M) и т.д.
Если корректируется значение константы, например, счетчика среднего разряда (коррекция "грубо"), то, в большинстве случаев, после нее, необходимо производить коррекцию значения константы счетчика младшего разряда (коррекция "точно").
В большинстве случаев, манипуляций с величинами констант счетчиков этих разрядов вполне достаточно, но, если требуется сформировать измерительный интервал времени (и вообще, любой калиброванный интервал времени) с максимально возможной точностью, а шаг изменения константы счетчика младшего разряда этого сделать не позволяет (вспомните, что минимальный полный цикл ПП задержки имеется в стандартной ПП задержки без "врезки", и он составляет 3 м.ц., а для подпрограмм с "врезками" он еще больше), то необходимо вставить в текст программы один или несколько NOPов, или убрать из текста программы один или несколько NOPов (если они имеются в тексте программы).
Эти NOPы вставляются в текст программы либо до группы команд, создающих счетчик, либо после нее (то есть, в непосредственной близости от этой группы команд).
Посмотрите в текст программы cus.
В группах команд, формирующих время отрицательного и положительного полупериодов Вы увидите группы NOPов. Это как раз тот случай. Более подробно с ними разберемся ниже.
То, что Вы прочитали, и есть один из вариантов отладки программы (вернее - "взлома" с последующей отладкой, необходимость которых возникает достаточно часто) для случая, когда необходимо только изменить какие-то времязадающие параметры устройства.
Единственного, чего в этом процессе не хватает - описания того, как это сделать на практике, работая в MPLAB.
Устраняем этот пробел.

Отладка временных характеристик устройства, обслуживаемого программой cus.
В отличии от случая, описанного выше, "взламывать" что-то в программе cus не нужно, так как конструкция этой программы понятна (я надеюсь на это), и заниматься работой по "вычислению" мест закладок констант и конструкции счетчиков, в которых они используются, не нужно.
Если предположить, что программа cus работоспособна (что и имеет место быть) и величину 3-хсекундного интервала времени корректировать не нужно, то ее отладка сводится к проверке и последующей точной коррекции величин интервалов времени отрицательного и положительного полупериодов.
Они должны быть одинаковыми (форма сигнала - "меандр") и быть равными точно 345 мкс. или 345 машинным циклам (кварц 4 мГц.).
Откройте проект cus (любой: cus_1 или cus_2).
Перед Вами - текст программы.
Ранее говорилось, что в состав симулятора входит секундомер. Это как раз тот случай, когда он нужен.
Чтобы что-то им измерять, необходимо: знать что измерять (это понятно - количество машинных циклов) и определить начальные и конечные точки замеров.
Если не сделать последнего, то можно просидеть перед компьютером хоть целый день, так и не дождавшись конца подсчета.
Таким образом, речь идет о назначении точек остановок.
Назначить их очень просто (об этом говорилось выше), сложность заключается в том, какие именно команды назначать точками остановок.
Это предполагает то, что программист должен либо свободно ориентироваться в тексте программы, либо, как минимум, знать все "ключевые точки" в тексте программы.
Для программы cus такими "ключевыми точками" являются команды bcf PortB,2 (начало формирования отрицательного полупериода) и bsf PortB,2 (начало формирования положительного полупериода).
Еще одна команда bcf PortB,2 , расположенная сразу же после команд старшего разряда 2-хразрядного счетчика, по причинам, указанным в 8-м разделе, к этим "ключевым точкам" не относится.
И в самом деле, если необходимо измерить время прохождения полупериода (периода) импульсной последовательности, его нужно мерить от перепада до перепада.
Итак, точки остановки четко определены: bcf PortB,2 и bsf PortB,2 Перед началом практической работы, необходимо уяснить следующее:
Если, в ходе работы над текстом программы (любой), появляется необходимость в его редактировании, а после этого редактирования нужно сохранить в проекте исходный текст программы, то, при закрытии проекта, когда Вам будет предложено ответить на вопрос: "Сохранить изменения или нет?", необходимо нажать на "No".
Напоминаю Вам, что, в обучающих целях, предполагается, что программа cus составляется "с чистого листа" и, в этом случае, в регистр Sec, в обоих случаях (как при формировании отрицательного, так и при формировании положительного полупериодов), закладывается ранее расчитанная константа .86 и NOPы точной калибровки отсутствуют.
Сделаем так: сначала, при уяснении механизма замеров, будем работать с полностью отработанным текстом программы, а затем, для иллюстрации процесса отладки, соответствующим образом изменим текст программы (уберем NOPы и заменим значения констант на расчетные) и произведем, после этого, ее отладку.
Обратите внимание: "ключевые команды" не являются первыми командами ни одной из подпрограмм и не помечены (меток нет), следовательно, назначить их точками остановок в окне Break Settings (вспоминайте: Debug - Break Settings...) не получится (загляните в это окно и убедитесь в этом).
Следовательно, точки остановок будем назначать, щелкая по строкам с этими командами (кто забыл - вернитесь назад и прочитайте, как это делается).
Еще одно напоминание: перед замером, все команды программы, находящиеся в тексте программы до первой точки остановки ("верхней"), должны быть исполнены.
Итак, первая ("верхняя") точка остановки "по ходу текста программы" - bcf PortB,2
Назначаем ее, щелкнув правой кнопкой мыши по строке с этой командой и выбрав в выпадающем списке Break.
Строка окрасилась в красный цвет и теперь видно, что эта команда является точкой остановки.
Теперь нужно "добраться" до этой команды, что предполагает исполнение всех предшествующих команд программы, начиная с первой команды ПП START.
Можно сделать это и вручную, щелкнув по кнопке сброса программы, а затем, щелкая по кнопке со следами, а можно "добраться" до этой команды и в "автомате", что гораздо быстрее.
Сделаем последнее:
Вызываем окно секундомера (Window - Stopwatch). Убедитесь, что в квадратике Clear On Reset галочка установлена.
Щелкаем по кнопке сброса программы, после чего Вы увидите, что рабочая точка программы перешла на ее начало. После этого секундомер должен показывать нули.
Запускаем "автомат" (автоматическое исполнение программы), щелкнув по кнопке с зеленым светофором.
В идеале, через некоторое время, рабочая точка программы должна остановиться на назначенной нами точке остановки. Во время работы в "автомате", внизу окна MPLAB должна высветиться желтая полоса, которая исчезнет при переходе рабочей точки программы на команду, назначенную "нижней" точкой остановки (точка конца замера).
В данном случае, этого не происходит: желтая полоса будет высвечиваться постоянно и также постоянно показания секундомера будут увеличиваться.
Можете не надеяться на остановку: программа в симуляторе "зависла", то есть, рабочая точка программы "гоняет" где-то в ней по кольцу и не может из него выйти, а, соответственно, и дойти до команды "нижней" точки остановки.
Так что, не все в этом деле так просто.
Давайте разбираться: каким же образом преодолеть это затруднение и выйти на точку остановки?
Для этого, прежде всего, необходимо ответить на вопрос: "Если программа обеспечивает нормальную работу реального устройства, то по каким причинам она может "зависнуть" в симуляторе?"
Ответ:
Причина простая: не симитированы внешние воздействия, то есть, либо на входе управления RB0, либо на входе управления RB6, либо на обоих этих входах, установлен такой уровень (уровни), который "заводит" рабочую точку программы в "вечное кольцо" (либо в ПП START, либо в ПП PRD).
Поочередно пощелкайте по кнопкам с зеленым и красным светофором и, в большинстве случаев, Вы обнаружите остановившуюся рабочую точку программы (после щелчков по красному светофору) в командах ПП PRD.
Вот Вам и то место программы, в котором рабочая точка "ушла" в "вечное кольцо".
Рассуждая далее, конечно, можно "вычислить", из-за чего это может произойти (кстати, это очень полезно, и, кто желает, тот может это сделать сейчас, отложив пока чтение в сторону).
Причину, по которой рабочая точка программы "не может добраться" до команды точки остановки, можно обнаружить очень просто, даже не прибегая к приведенным выше рассуждениям.
Сбросьте программу на начало и перейдите к пошаговому ее исполнению, то есть, пощелкайте по кнопке со следами (или по F7 на клавиатуре).
Одновременно следите за движением рабочей точки (то есть, за исполнением программы) в текстовом редакторе MPLAB.
Вы заметите, что, по команде goto PRD, произойдет переход в ПП PRD, где рабочая точка и "уйдет" в "вечное кольцо".
Такое может быть только в единственном случае: когда на выводе RB6 установлен 0.
Имейте ввиду: мы работаем не с каким-то реальным (физическим) устройством, а с виртуальным, то есть, с тем, что создано (просимулировано) "внутри" MPLAB.
Следовательно, "корень зла" нужно искать в симуляторе.
Давайте посмотрим, что выставлено симулятором на выводах порта В по умолчанию.
Сбросьте программу на начало.
Щелкните по кнопке SFR.
В окне Special Function Register Window обратите внимание на содержимое регистра PotrB - по умолчанию выставлены все нули (это относится и к PortA).
И сразу же все "становится на свои места": если 0-й и 6-й биты регистра PortB равны 0, то рабочая точка программы должна "проскочить" первую проверку (передача вкл/выкл), не "закольцевавшись" в ПП START, но, при второй проверке, она обязательно "уйдет" в "вечное кольцо" ПП PRD, где и будет "гонять" по кольцу "до скончания века", что мы уже и наблюдали.
Напрашивается естественный вывод: необходимо каким-то образом выставить в 6-м бите регистра PortB единицу, и тогда все будет OK.
В MPLAB это вполне можно сделать, если задействовать так называемые функции стимула (асинхронный стимул, стимул порта ввода - вывода и стимул регистра - на выбор).
Это вещь, конечно, хорошая и, в будущем, постараюсь объяснить, что это такое, но, если сейчас я буду "забивать Вам этим голову", то, опасаюсь, что Вы просто запутаетесь и "отклонитесь от генеральной линии", что не есть хорошо.
Поступлю проще и эффективнее.
Помните, я ранее говорил о том, что при работе в симуляторе существуют некоторые "уловки"?
Они-то, как раз, и позволяют как-бы "обмануть" MPLAB в том смысле, что позволяют обойтись и без задействования функций стимула.
Принцип такого "обмана" очень простой: команда, с которой начинается "бардак", из текста программы исключается.
На практике это делается так:
Трудности создает команда goto PRD: именно она направляет рабочую точку программы в "вечное кольцо".
Заменяем эту команду на команду nop.
Так как, позднее, команду goto PRD нужно будет опять вернуть на свое место (а nop, соответственно, убрать), то удобнее это сделать так: непосредственно перед goto поставить точку с запятой (после этого MPLAB перестает "видеть" эту команду) и пробелами сместить всю строку вправо (в "район" комментария), освободив "место" для команды nop, после чего, во втором столбце этой строки, "настучать" nop.
Позднее, MPLAB все-равно "заставит" Вас проассемблировать текст программы, так что лучше, сразу же после внесения изменения (любого) в текст программы (а мы его внесли), проассемблировать его, а, заодно, и убедиться в отсутствии ошибок (Вы получите сообщение о безошибочном ассемблировании).
Что получилось?
А получилось то, что, после выполнения команды btfss PortB,6, рабочая точка программы, через nop , "перескочит" на команду bcf PortB,2 и ее "ухода" в "вечное кольцо" ПП PRD не произойдет.
А раз это так, то мы без проблем "доберемся" до назначенной точки остановки.
Естественно, возникает вопрос: "Если команда goto исключена из текста программы, то не отразится ли это на значениях калиброванных интервалов времени полупериодов?"
В данном случае - не отразится.
Объяснение: не смотря на то, что 6-й бит регистра PortB установлен в 0, реально исполняется (имитируется) сценарий работы для случая установки 6-го бита регистра PortB в 1.
И в самом деле, рабочая точка программы переходит на команду bcf PortB,2 через nop, но только этот nop не "виртуальный" (см. объяснение работы команд ветвления, которое было дано ранее), а "реальный".
Проще говоря, "виртуальный" NOP заменен на "реальный" ("что в лоб, что по лбу - все едино").
Теперь можно без проблем "добраться" до назначенной точки остановки как в "автомате", так и пошагово исполняя программу.
Попробуйте сначала в "автомате": сбросьте программу на начало и щелкните по кнопке с зеленым светофором.
После отработки "автомата" Вы увидите: рабочая точка программы "встала" на назначенную точку остановки (bcf PortB,2), что и требовалось.
Итак, мы "вышли" на начало замера.
Посмотрите на зафиксированные показания секундомера.
Вы видите количество машинных циклов, отработанных от начала программы и до установки рабочей точки программы на "верхнюю" точку остановки (начала замера) и их перевод в время.
Теперь понаблюдайте, что происходит при пошаговом исполнении программы: сбросьте программу на начало и пощелкайте по кнопке со следами или по клавише F7.
Обратите внимание на процесс счета машинных циклов секундомером.
Вы увидите, что, при выполнении команд занимающих 1 м.ц., показание счетчика машинных циклов будет увеличиваться на 1, а при выполнении команд, занимающих 2 м.ц., показание счетчика будет увеличиваться на 2.
В будущем, если у Вас возникнут затруднения в определении количества машинных циклов исполнения той или иной команды или группы команд (что более актуально), то их можно устранить, пошагово исполняя команду (команды) и сверяясь с показаниями счетчика.
Обратите также внимание на то, что, при пошаговом исполнении программы, установленные точки остановки игнорируются, что указывает на то, что точки остановки выполняют свои функции только при работе в "автомате".
Вывод: описанный выше прием позволяет отказаться от использования достаточно трудоемких функций стимула и существенно упростить выход рабочей точки программы на точку остановки при возникновении затруднений типа ее ухода в "вечное кольцо" до достижения точки остановки.
Таким образом, производя несложные манипуляции (с учетом специфических особенностей той или иной команды), можно направить движение рабочей точки программы в нужный для программиста сценарий.
Естественно, при этом нужно знать, как работает программа и четко представлять себе механизм работы той или иной команды (особенно - команд ветвления).
Лично я, прибегаю к помощи функций стимула очень редко и, в основном, работаю по изложенному выше принципу, и это не есть проявление лени, а, скорее всего, наоборот, так как такого рода "тренировка мозгов" исключительно полезна и очень помогает в понимании "глубинной сути" работы той или иной программы.
Для полной ясности, еще раз уточняю: на команду, назначенную точкой остановки, можно "выйти" 2-мя способами:
Первый: в режиме пошагового исполнения программы.
При этом точку остановки можно не назначать (а какой смысл? Она же игнорируется).
Этот режим применяется при небольшом количестве команд, находящихся между первой командой программы и командой, на которой нужно остановиться.
В случае, если эта группа команд "объемиста" или в ее состав входит (входят) ПП задержки со значительным временем задержки, то "выход" на команду, на которой нужно остановиться, лучше делать в "автомате".
Второй: в "автомате".
В этом случае обязательно нужно назначить точку остановки.
Итак, мы "стоим" на точке остановки bcf PortB,2, то есть, на той команде, после исполнения которой начинается формирование отрицательного полупериода.
Обращаю Ваше внимание на следующий общий принцип: то, что рабочая точка программы "стоит" на команде, означает, что исполнилась предыдущая команда, а команда, на которой "стоит" рабочая точка программы, еще не исполнена.
Проще говоря, команда bcf PortB,2 исполнится только тогда, когда рабочая точка программы "прыгнет" на следующую, после нее, команду.
Рассуждаем дальше: начальная точка замера имеется, а конечной точки замера нет, значит ее нужно, опять же, назначить.
Естественно, ей должна быть команда смены нуля на единицу на выводе RB2 (bsf PortB,2).
Щелкаем по строке с этой командой правой кнопкой мыши и выбираем Break.
Итак, рабочая точка программы "стоит" в начале отрицательного полупериода (на "верхней" точке остановки), а "нижняя" точка остановки установлена в его конце.
А теперь все просто: в окне секундомера щелкаем по кнопке Zero (сбрасываем показания секундомера в ноль) и, далее, по кнопке с зеленым светофором.
Дожидаемся установки рабочей точки программы на назначенную ранее точку остановки bsf PortB,2 (отработки "автомата") и снимаем показания секундомера.
В строке Time секундомера Вы увидите время формирования отрицательного полупериода.
Если Вы сделаете этот замер в окончательно отработанном тексте программы cus_1 или cus_2, то увидите, что время формирования отрицательного полупериода точно равно расчетному времени - 345 мкс.
Технология замера времени положительного полупериода следующая:
Сначала снимаем все точки остановки (Debug - Clear All Points), а затем устанавливаем точку остановки на команде bsf PortB,2
В "автомате" переходим на эту точку остановки.
Устанавливаем точку остановки на команде bcf PortB,2
Сбрасываем секундомер на ноль (Zero).
Щелкаем по кнопке с зеленым светофором и ждем окончания процесса.
Смотрим на показания секундомера, время положительного полупериода должно быть равным 344 мкс. (для замера в окончательно отработанном тексте программы).
В данном случае, при отладке программы cus_1 или cus_2, в обучающих целях, я посчитал нужным "округлить" значение периода в сторону уменьшения: точное значение периода =689,6 мкс., а получилось 345+344=689 мкс.
Если нужно "округлить" значение периода в сторону увеличения (до 690 мкс.), то время положительного полупериода нужно увеличить на 1 мкс. (345+345=690 мкс.), что можно сделать или, добавив еще один nop, например, после команды bsf PortB,2 (или перед командой movlw .83), или, вообще, убрав все 3 NOPа и увеличив при этом величину константы с .83 до .84 (информация к размышлению: полный цикл ПП PAUSE_2 равен 4 м.ц.).
Говоря это, я немного забегаю вперед, и если сейчас это не понятно, то, после объяснения процесса отладки программы, "все встанет на свои места".
Итак, на отлаженной программе, я показал Вам, как проверить значения времени формирования отрицательного и положительного полупериодов.
Отлаживать тут нечего, так как программа уже ранее отлажена.
Теперь нужно понять, что такое отладка программы.
Предположим, что мы работаем с текстом программы, написанной "с чистого листа".
Чтобы перейти к такому тексту, нужно несколько видоизменить текст программы cus_1 или cus_2.
Вносим изменения, оговоренные выше.
Уберите все NOPы (6 штук), которые находятся после команд bcf PortB,2 и bsf PortB,2 (можно или поставить перед ними точки с запятыми, или вообще убрать их из текста программы: первое - удобнее, а последнее - нагляднее).
В командах закладки констант замените .85 на .86 и .83 на .86 (.86 - расчетное значение).
Изменения в текст программы внесены, следовательно, его нужно проассемблировать.
Делаем это и убеждаемся, что ошибок нет.
Итак, как бы "забываем" про отлаженный текст программы, с которым работали ранее.
Перед нами текст программы "с чистого листа", то есть, текст, в котором калибровочные константы времени отрицательного и положительного полупериодов установлены "на глазок", то есть - не точно.
Задача: необходимо установить интервалы времени формирования отрицательного полупериода равным 345 мкс., а положительного - 344 мкс.
Все начинается с проверки величин этих интервалов времени, формируемых при исполнении данного ("неотлаженного") текста программы, и это понятно, так как, для ориентации, нужны некие "начальные точки отсчета".
По ходу исполнения программы, первым формируется отрицательный полупериод, значит с него и нужно начать.
Предполагаю, что Вы изучили изложенную выше информацию и потренировались в установке и снятии точек остановки, а также разобрались с секундомером.
После сброса на начало программы, "дойдите" до команды bcf PortB,2 либо в режиме пошагового исполнения программы, либо в "автомате" (описано выше).
Итак, рабочая точка программы находится на команде bcf PortB,2 (начало формирования отрицательного полупериода).
Вызываем секундомер и сбрасываем его на ноль.
Начальная точка отсчета имеется. Назначаем конечную точку отсчета, то есть, назначаем точкой остановки команду bsf PotrB,2 (начало формирования положительного полупериода).
Щелкаем по кнопке с зеленым светофором и ждем окончания процесса подсчета.
Смотрим на показания секундомера. Они равны 346 мкс.
А далее все просто: константу нужно уменьшать.
Если уменьшить ее на 1, то есть, сделать равной .85, то время формирования отрицательного полупериода уменьшится на 4 мкс. (полный цикл стандартной ПП задержки с "врезкой" из одной команды, выполняющейся за 1 м.ц., составляет 4 м.ц.) и составит 342 мкс.
"Недобор" в 3 мкс. устраняем добавлением 3-х NOPов либо сразу же после команды начала формирования отрицательного полупериода (bcf PortB,2), либо сразу же после команды goto PAUSE_1.
Можно также поставить эти NOPы после команды movwf Sec или между командами movlw .85 и movwf Sec.
А теперь "вспомните" отстроенный текст программы - вот и ответ на вопрос: откуда что взялось?
В данном случае, я вставил NOPы после команды bcf PortB,2.
Все, получилось то, что требовалось - 345 мкс., можете проверить по секундомеру.
Измеряем время положительного полупериода.
Сбрасывать программу на начало и снимать точки остановки не нужно.
Если Вы "добирались" до команды bcf PortB,2 в режиме пошагового исполнения программы (вспомните, в этом случае точку остановки устанавливать не обязательно), то сделайте второй точкой остановки команду bcf PortB,2.
Если Вы "добирались" до команды bcf PortB,2 в "автомате", то она уже является точкой остановки и делать ничего не нужно.
Сбрасываем секундомер на ноль.
Щелкаем по кнопке с зеленым светофором и ждем окончания подсчета.
Получаем 353 мкс.
Нужно - 344 мкс. Разница - 9 мкс.
Если уменьшить величину константы на 2 (минус 4*2=8 мкс.), то опять будет "перебор", а одну команду из текста программы удалить никак нельзя, они все нужны.
Следовательно, необходимо уменьшить величину константы на 3, а "недобор" скомпенсировать добавлением 3-х NOPов, что Вы и видите в тексте отлаженной программы.
Из этого следует общий принцип: если существует "перебор" и нет возможности скомпенсировать его удалением из текста программы соответствующего числа команд (бывают случаи, когда это возможно), то величина константы изменяется таким образом, чтобы возник минимальный "недобор", который компенсируется добавлением соответствующего количества NOPов.
Все, процесс отладки программы cus закончен. Точки остановки можно снять.
Если Вы выполнили все указанные выше манипуляции, то, в результате, получится исходный текст отлаженной программы.
Теперь необходимо устранить последствия "уловки": убрать "обманный" nop и поставить (вернуть на свое бывшее место) на его место команду goto PRD.
Остается только проассемблировать текст программы (так как были внесены изменения) и, после этого, как говорится, "со спокойной совестью" можно открывать, созданный при этом ассемблировании, HEX - файл программы, в программе, обслуживающей Ваш программатор, и работать с ним далее ("зашить" в ПИК).
Описанный ранее процесс предварительного, "грубого" расчета значений времязадающих констант и последующей отладки программы, достаточно прост, но не все так просто.
А если имеется "мощная врезка" и расчет полного цикла ПП задержки трудоемок или вызывает затруднения?
А если, кроме того, в эту "врезку" входят одна или несколько циклических ПП задержки со своими "врезками"?
В этом случае, можно просто "утонуть" в предварительных расчетах, потратив на это уйму времени и без полной гарантии успеха.
Способ преодоления такого рода затруднений, как это ни странно, также прост.
Например, имеется 3-хразрядный счетчик, при помощи которого необходимо сформировать какой-то калиброванный интервал времени.
Из-за "навороченности" программы у Вас возникли затруднения с предварительными расчетами значений констант или Вам просто лень или не хочется заниматься такими расчетами.
Что нужно делать?
В старшем и среднем разрядах счетчика выставляем значения констант из расчета формирования минимального интервала времени.
Если разряд счетчика - вычитающий, то ставится константа, например, .01 или .02, а если суммирующий, то .254 или .255.
Это делается для удобства - чтобы подсчет времени в секундомере происходил быстро и не нужно было бы долго ждать его конца.
Для младшего разряда счетчика назначаем и устанавливаем константу "от балды", например, .10 (большое значение выставлять не нужно, так как, в этом случае, придется долго ждать окончания подсчета).
"Выходим" на "верхнюю" точку остановки, назначаем "нижнюю" точку остановки, сбрасываем секундомер в ноль и запускаем "автомат".
Ждем окончания подсчета и фиксируем показания секундомера.
Например, получилось 1250 мкс.
В младшем разряде счетчика увеличиваем значение константы с .10 до .11 .
Делаем те же манипуляции.
Получилось, например, 1353 мкс.
Вычисляем разницу: 1373-1250=123 мкс.
Вывод: шаг изменения константы младшего разряда составляет 123 мкс., следовательно, полный цикл счета счетчика младшего разряда составляет 256*123=31488 мкс.
Это означает, что через каждые 31488 мкс. содержимое счетчика среднего разряда будет изменяться на единицу.
Следовательно, полный цикл счета счетчика среднего разряда составит 256*31488=8,060928 сек.
Это означает, что через каждые 8,060928 сек. содержимое счетчика старшего разряда будет изменяться на единицу.
Следовательно, полный цикл счета счетчика старшего разряда составит 256*8,060928=2063,5975 сек.
Например, требуется сделать "прикидку" значения констант для вычитающего 3-хразрядного счетчика, формирующего интервал времени 20 сек.
В старшем разряде тройку назначать нельзя, так как будет "перебор", а двойка будет в самый раз: 2*8,060928=16,121856 сек.
20-16,121856=3,878144 сек.
Руководствуясь таким же принципом, для среднего разряда счетчика назначаем величину константы .123: 123*31488=3873024 мкс.=3,873024 сек.
3,878144 сек.- 3,873024 сек.=0,00512 сек.=5120 мкс.
Для младшего разряда счетчика, приблизительно, назначаем константу .41: 41*123=5043 мкс.
Итак, "грубая прикидка" значений констант, для формирования интервала времени величиной 20 сек., дала следующие результаты: значения констант 3-хразрядного счетчика старшего разряда - .02 (H), среднего - .123 (M), младшего - .41 (L).
Обращаю Ваше внимание, это именно "грубая прикидка" и не только по той причине, что для младшего разряда назначена константа приблизительно, но и по причине того, что участок программы, на котором производится подсчет, часто содержит группы команд, выполняющихся последовательно, то есть, не являющихся циклическими (например, подготовительные операции или те же команды установки констант и т.д.).
Вернемся к рассмотренному выше примеру: при значении константы младшего разряда счетчика .10 и шаге в 123 мкс., счетчик, казалось бы, должен показать 1230 мкс., но он показывает 1250 мкс. В чем дело?
А дело в том, что в этот пример я заложил реально встречающуюся ситуацию, которая иллюстрирует только что сказанное.
Все очень просто - между начальной точкой измерения и командами, реализующими счетчик младшего разряда находится группа команд, выполняющихся в течение 20 м.ц.
Такие "вкрапления" могут быть "разбросаны" в различных местах участка измерения и, естественно, они снижают точность описанного выше способа определения величин констант, но, в большинстве своем, несущественно.
В подавляющем своем большинстве, такие группы команд "слабосильны" в том смысле, что, по сравнению с циклическими подпрограммами, выполняются очень быстро и, по этой причине, в большинстве случаев, они, если и вносят погрешность, то такую, которая позволяет достаточно точно выйти в "сектор обстрела".
В нашем случае, если имеются такие "вкрапления", реальная величина формируемого интервала времени может оказаться не 20 сек., а, например, 20,0017сек., то есть, больше расчетного.
Остается только, при помощи секундомера, скорректировать значения констант (начиная с младшего разряда. Во многих случаях достаточно только коррекции значения константы младшего разряда) и максимально приблизиться к расчетной величине, используя NOPы.
В случае значительных различий между реальной и расчетной величиной времени задержки, необходимо замерить шаги констант среднего и/или старшего разрядов (в примере мы их расчитывали исходя из шага младшего разряда) и внести в расчеты соответствующие коррективы.
Заверяю Вас, ничего особо сложного в выполнении описанных выше процедур нет.
Когда Вы "набьете на этом деле руку", все это будет происходить без "напряга", хотя и будет занимать определенное время, кстати, гораздо меньшее, чем если Вы будете скурпулезно, на бумаге, подсчитывать величины полных циклов циклических ПП, так что, подобного рода манипуляции, нужно обязательно освоить и, самое главное, понимать смысл производимых действий.
Потренироваться можно на тексте все той же программы cus.
Если есть желание, то попробуйте изменить величину 3-хсекундного интервала выхода сигнала тонального вызова в эфир, например, до 0,500000 сек. и максимально приблизиться к этому значению.
Большее время задавать не нужно - будете долго ждать конца счета.
Попробуйте сами назначить точки остановки. Не забудьте про "уловку".

Разбираем следующий случай: в тексте программы имеется ошибка.
Если это ошибка не функционального характера, то MPLAB обязательно на нее укажет.
Например, Вы забыли "прописать" регистр, а в тексте программы есть обращение к нему или допустили орфографическую ошибку при написании команды, или забыли указать место сохранения результата операции, или забыли указать номер бита, с которым производится действие, или орфография названия регистра в рабочей части программы не соответствует орфографии "прописанного" названия этого же регистра, или один столбец "наезжает" на другой, или Вы написали строку с комментариями и не поставили перед ней точку с запятой и т.д.
В этом случае, при ассемблировании текста программы, Вы получите сообщения о всех допущенных ошибках с указанием строк программы, в которых они допущены.
Двойной щелчок по строке с ошибкой, и курсор покажет Вам, в текстовом редакторе MPLAB, строку текста программы, в которой она допущена.
Тут, как говорится, и комментировать нечего: нужно эти ошибки устранить и добиться выдачи сообщения о безошибочном ассемблировании.
При этом вовсе не обязательно, за один раз, устранять все ошибки "оптом".
Можно не спеша и по очереди разобраться с каждым сообщением об ошибке, производя ассемблирование после каждой такой "разборки" или после неудачной ее попытки.
Пока все подобные ошибки не будут устранены, дальнейшая работа с текстом программы просто не имеет смысла.
Советую Вам попробовать внести в текст программы cus какие-нибудь ошибки, из тех, что указаны выше, и посмотреть, что из этого получится после ассемблирования.
Предположим, что все ошибки подобного рода устранены и получено сообщение о безошибочном ассемблировании.
После этого, "хлопать в ладоши", конечно, рановато, так как далее предстоит "борьба" с ошибками функционального характера (естественно, при их наличии).
Ошибки функционального характера возникают из-за непродуманности как самой блок - схемы программы, так и способа реализации ее "квадратиков" или связей между ними.
Например, рабочая точка программы "зависла" в какой-то циклической ПП и "не хочет из нее выходить" даже при соблюдении всех условий ее выхода из этой ПП или выполнение какой-то ПП игнорируется, не смотря на то, что она должна исполняться и т.д.
Такого рода ошибок может быть великое множество и диапазон их причин простирается от элементарной невнимательности, когда можно обойтись "малой кровью", и до ошибок стратегического характера, когда нужно производить "капитальный ремонт" всей программы.
Суть поиска таких ошибок сводится, в основном, к проверке исполнения программы с обязательным контролем соответствия фактических результатов тех или иных действий, предполагаемым (расчетным) результатам, которые реализуют замысел программиста.
При отсутствии такого соответствия, выявляется причина несоответствия и принимаются меры по ликвидации этого несоответствия.
Анализ текста программы на наличие или отсутствие ошибок функционального характера целесообразно начать с "прогонки" рабочей точки по всему полному циклу программы.
В этом случае, работа происходит по принципу "следопыта".
Первичная задача на этом начальном этапе - "протащить" рабочую точку по полному циклу программы, начиная с первой ее команды, при настройках MPLAB по умолчанию (если "разборка" с этими настройками предполагается по ходу выполнения программы) и до возникновения первого затруднения, которое, затем, анализируется и устраняется.
После устранения первого затруднения, рабочая точка программы "протаскивается" дальше - до возникновения второго затруднения и т.д. - до конца полного цикла программы и выхода на новый полный цикл программы.
Факт такого "протаскивания" рабочей точки по полному циклу односценарной программы, подобен проверке работоспособности программы "грубо" и говорит о том, что программа "жизнеспособна", после чего можно приступить к более детальной проверке указанного выше соответствия с привлечением всего "арсенала удобств", предоставляемого MPLAB.
После того, как программист убеждается в соответствии фактического алгоритма работы программы расчетному (задуманному им), можно перейти к отладке программы.
Если программа имеет несколько сценариев работы, то нужно "пройтись" по их максимально возможному количеству (в идеале - по всем).
Дело в том, что симулятор не всесилен и встречаются такие сценарии работы программы, которые сложно или вообще невозможно в нем проверить (в большинстве своем - из-за сложности или невозможности имитации внешних воздействий).
В этом случае, после каждого изменения текста программы необходимо ее ассемблировать, "зашивать" в реальный ПИК и "смотреть, что из этого получится".
Есть, конечно, такие "штуковины" под названием эмуляторы, но как глянешь на их цены - становится очень грустно, так что лучше представить себе, что их нет и полагаться, в этих затруднительных случаях, на свое мастерство.
Обращаю Ваше внимание, что употребленное мной слово "затруднение" не является, однозначно, ошибкой функционального характера.
Оно может быть ей, но может быть также и просто "уходом" рабочей точки программы в нежелательный сценарий работы программы.
Вспомните то, как при отладке программы cus рабочая точка программы "ушла в вечное кольцо" ПП PRD и из-за этого нельзя было осуществить замер значений полупериодов.
Ошибки, как таковой, в этом случае не было, просто внешнее воздействие было таким, что исполнение программы происходило по нежелательному (для замера) сценарию.
Это вовсе не говорит о том, что этот нежелательный сценарий можно вообще "сбросить со счета".
Проверить нужно и его, только при следующей "прогонке", осуществляемой именно для проверки этого сценария.
В зависимости от сложности программы, таких специализированных "прогонок" может быть достаточно большое количество, так что освоение их технологии (свободная "рулежка" сценариями программы) - просто насущная необходимость.
Чем больше программист знает прямых или косвенных способов имитации внешних управляющих сигналов, тем в меньшее количество затруднительных ситуаций, связанных с переходами из одного сценария исполнения программы в другой, он будет попадать.
Вот Вам и ответ на вопрос: зачем нужны функции стимула или "уловки", одну из которых я описал при отладке программы cus.
В следующем разделе будет показано, как на практике осуществляется такая "рулежка".
Переходите к следующему разделу.


Kea_30

10. Как отследить выполнение программы

Важность этой процедуры очевидна. Отслеживание выполнения программы это та "палочка - выручалочка", которая помогает выйти из многих затруднительных ситуаций.
Для того чтобы качественно исполнить эту процедуру, необходимо, прежде всего производить ее осознанно, имея перед собой некую конкретную цель.
В различных случаях, эта цель также различна, и она зависит как от замысла программы, так и от анализируемого сценария ее исполнения, но набор приемов отслеживания выполнения программы ограничен.
Освоение этих приемов, хотя и требует определенных усилий, но не является чем-то уж очень сложным.
Манипулируя этими приемами, можно составить ясное, четкое и, в большинстве случаев, детальное представление о том, исполняется ли программа в соответствии с задуманным алгоритмом ее работы или "где-то что-то пошло наперекосяк" и по какой причине такой "перекосяк" произошел.
Давайте с этим разбираться.
Самый основной из этих приемов - отслеживание перемещения рабочей точки программы по тексту программы.
Оно может происходить в текстовом редакторе MPLAB или в окне Program Memory Window (вызывается кнопкой ROM).
Плюсы отслеживания в окне Program Memory Window заключаются в том, что в этом окне все команды программы располагаются очень компактно, с указанием их порядковых номеров и адресов в памяти программ, а минусы: названий регистров Вы там не найдете (вместо них указаны их адреса в области оперативной памяти) и все числа - только в 16-ричной форме исчисления.
Обычно, отслеживание движения рабочей точки программы производится в текстовом редакторе, так как, в этом случае, оно более комфортно, а окно Program Memory Window "вызывается" тогда, когда нужно узнать порядковый номер исполняемой команды или ее адрес в памяти программ или адрес регистра, к которому обращается команда.
Кроме того, в окне Program Memory Window можно назначить и точки остановки.
Технология этого назначения - такая же, как и при назначении точек остановки в текстовом редакторе: щелчок правой кнопкой мыши по строке с командой и выбор (щелчок левой кнопкой мыши) из списка строки Break.
После этого, выбранная строка (в окне Program Memory Window) окрашивается в красный цвет, и в текстовом редакторе Вы увидите то же самое (как будто бы Вы назначили точку остановки в текстовом редакторе).
После того, как окно Program Memory Window оказывается ненужным, его можно закрыть и снова открыть, если оно потребуется.
Окно Special Function Register Window (кнопка SFR).
Это окно оказывается очень полезным при работе с программами, которые управляются внешними сигналами (а таких - большинство).
Дело в том, что, если Вы заранее не предприняли мер по установке необходимых Вам уровней этих внешних управляющих сигналов (функции стимула не задействованы), то MPLAB "тупо" выставит на этих выводах свои уровни (по умолчанию).
Причем, это относится не только к регистрам PortA и PortB, но и вообще ко всем регистрам специального назначения.
Откройте программу cus (сбрасывать ее на начало и исполнять не нужно, просто откройте и все).
Щелкните по кнопке SFR.
Откроется окно Special Function Register Window, в котором Вы увидите эту самую настройку по умолчанию.
В основном, в регистры специального назначения записаны нули, за исключением 4-х регистров, строки которых окрашены в красный цвет.
Во всех разрядах регистра TrisB записаны единицы, следовательно, все выводы порта B "виртуального" ПИКа работают на вход. То же самое относится и к выводам порта А. Так как их всего 5, то выставлять единицы в оставшихся 3-х нерабочих разрядах, разработчики MPLAB посчитали нецелесообразным и оставили там нули.
В регистре Option также выставлены все единицы. Чем руководствовались при этом разработчики MPLAB - остается только гадать, это просто нужно "принять как данность".
Чему это соответствует - определите сами по распечатке регистра Option.
В регистре Status, 3-й и 4-й биты установлены в 1, что соответствует, якобы, прохождению сброса по включению питания ("якобы" - потому что ПИК "виртуальный").
Если сбросить программу на начало, то в окне Special Function Register Window ничего не изменится, за исключением того, что красное выделение пропадет.
Из оставшихся настроек "по умолчанию", прежде всего, необходимо обратить внимание на нули в регистрах PortA и PortB.
Ранее мы уже сталкивались с тем, что из-за этого возникали затруднения при отладке программы cus, и которые могут возникать, по этой же причине, при отладках других программ.
Вывод из этого простой: если при исполнении команд, осуществляющих манипуляции с содержимым регистров PortA или PortB возникают затруднения, то необходимо открыть окно Srecial Function Register Window, посмотреть на содержимое этих регистров на момент исполнения команды и сделать соответствующие выводы.
При этом не исключено, что производится операция с содержимым этих регистров, установленным по умолчанию, и именно оно (содержимое) вызывает затруднение.
Что касается остальных регистров, то нужно просто учитывать, что, если с их содержимым, по ходу исполнения программы, не производилось никаких манипуляций, то в них записаны нули.
По ходу исполнения программы, начиная с первой ее команды, содержимое задействованных в программе регистров специального назначения, естественно, будет изменяться.
Таким образом, можно отследить все происходящие изменения содержимого регистров специального назначения и их соответствие задуманному.
При этом, строки, в которых находятся регистры, содержимое которых изменилось при исполнении текущей команды, выделяются красным цветом, что очень удобно с точки зрения акцентирования внимания именно на произошедших изменениях.
Так как содержимое регистров специального назначения удобнее всего воспринимать в бинарной системе исчисления, то окно Special Function Register Window удобно открывать при необходимости переводов чисел из 10-чной и 16-ричной систем исчисления в бинарную (и наоборот).
Основным окном детального отслеживания выполнения программы является окно File Register Window (кнопка RAM).
Оно удобно тем, что в нем можно "оптом" отследить все изменения, происходящие как с содержимым регистров специального назначения, так и с содержимым регистров общего назначения по той причине, что контролируется вся область оперативной памяти.
Если Вы сравните содержимое регистров специального назначения, расположенных в этом окне с содержимым регистров специального назначения, расположенных в окне Special Function Register Window (в 16-ричной системе исчисления), то обнаружите полное соответствие, причем, содержимое указанных выше 4-х регистров специального назначения (учтите, что регистр STATUS дублируется), будет также выделено красным цветом (после открытия проекта, вызова окна File Register Window и до сброса программы на начало).
После сброса программы на начало красное выделение содержимого этих регистров снимается.
Указанное выше полное соответствие наблюдается и по ходу исполнения программы, что позволяет, в течение основного времени отслеживания работы программы, пользоваться только окном File Register Window, открывая окно Special Function Register Window только в случае возникновения необходимости.
С учетом того, что в окне File Register Window также отслеживаются адреса регистров специального и общего назначения, это окно наиболее информативно и, по этой причине, является наиболее употребляемым.
Неудобство его применения связано с тем, что названия регистров специального назначения в нем не указываются (указываются адреса) и это, на первых порах, может вызвать затруднения в ориентации, что вовсе не "смертельно".
Держите перед глазами распечатку области оперативной памяти и, со временем, Вы просто запомните "где что лежит" и сможете обходиться без нее.
Что касается названий и адресов регистров общего назначения (их присваивает программист), то для каждой программы они различны.
Их можно вписать, например, карандашом в соответствующие квадратики в распечатке области оперативной памяти и, при возникновении необходимости в определении положения того или иного регистра общего назначения в области оперативной памяти (а, значит, и в окне File Register Window), просто сверяться с этой распечаткой.
Естественно, по ходу исполнения программы, содержимое таблицы окна File Register Window (в части касающейся задействованных по ходу исполнения программы регистров специального и общего назначения), будет меняться.
Если, при исполнении текущей команды, происходит изменение содержимого любого из этих регистров, то оно (значение числа в 16-ричной форме исчисления) выделяется красным цветом.
Если, при выполнении следующей команды, изменения их содержимого не происходит, то выделение красным цветом снимается.
Таким образом, можно комфортно отследить изменения, происходящие в области оперативной памяти и их соответствие задуманному.
Обращаю Ваше внимание: так как содержимое регистров общего назначения нулевого банка дублируется (отображается) в первом банке, то на последние 4 строки таблицы окна File Register Window можно не обращать внимания (чтобы его не "распылять"), а оставить только 1-ю строку 1-го банка с регистрами специального назначения.
Лично я изменяю размер этого окна таким образом, чтобы нижняя граница окна MPLAB "наехала" на эти 4 строки окна File Register Window и закрыла их, а само это окно помещаю в правом нижнем углу окна MPLAB.
Что касается четвертой кнопки (с очками), то "жать" на нее имеет смысл только в том случае, если необходимо отследить содержимое регистра W (в большинстве своем) или F (содержимое регистра W в других окнах не отслеживается).
В этом окне, конечно же, можно отследить содержимое и всех остальных регистров, но гораздо удобнее это сделать в окне File Register Window.
К помощи 4-й кнопки можно обратиться также в случае, если необходимо сохранить содержимое выбранных из списка регистров (в тот или иной момент исполнения программы) в виде отдельного файла.
Может быть, для кого-то это и окажется полезным, но, лично у меня, никогда не возникало такой потребности.
Если возникает потребность в отслеживании содержимого регистра W (F), то он заносится в окно Watch (как это сделать, описывалось выше).
Если, в этом случае, предельно уменьшить размеры этого окна, то оно получается очень компактным и занимающим мало места.
По этой причине, его можно один раз открыть и не закрывать до конца работы.

А теперь, на практике, отследим выполнение "учебно - тренировочной" программы cus. Откройте любой из проектов cus_1 или cus_2.
Предположим, что работать будем с отлаженной программой (команда goto PRD на nop не заменялась).
Примечание: для краткости изложения, в дальнейшем, я буду заменять названия окон названиями кнопок, при помощи которых они открываются:
- Program Memory Window на ROM,
- File Register Window на RAM,
- Special Function Register Window на SFR,
- Watch - без изменения (и кнопка, и окно).
Наводим порядок на "рабочем столе":
Если "лист" текстового редактора раскрыт полностью, то уменьшите его размеры, отодвинув к центру правый и нижний срезы этого "листа" на 1 - 2 см.
В этом случае, всегда будут видны "краешки" других открываемых нами окон (если они неактивны и находятся за "листом" текстового редактора) и проблем с их активацией не будет.
Открываем окно RAM и перемещаем его в правый нижний угол окна MPLAB.
"Цепляемся" за верхний срез окна RAM и двигаем его вниз до тех пор, пока нижние 4 строки таблицы перестанут быть видны.
Создаем окно Watch, "заложив" в него регистр W.
Размещаем это окно выше окна RAM (нижний срез окна Watch - "впритык" к верхнему срезу окна RAM) и правый срез окна Watch "двигаем вправо до упора".
"Цепляемся" за верхний левый угол окна Watch и уменьшаем размер этого окна до минимально возможного.
"Прожиточный минимум" выставлен, остальные окна будем открывать по мере необходимости.
Активация окон происходит обычным для Виндов способом, то есть, щелчком в любом месте окна.
Сбрасываем программу на начало.
Естественно, детально отследить работу программы можно только при пошаговом ее исполнении, так что щелкаем по кнопке со следами или жмем F7.
Для краткости, "привяжемся" к кнопке F7 клавиатуры.
Итак, жмем на F7 и переходим на первую команду ПП START.
То, что рабочая точка программы "встала" на команду clrf IntCon, означает, что исполнилась не эта команда, а предшествующая, то есть, команда goto START в "шапке" программы (вспоминаем предшествующий материал).
Смотрим в окна RAM и Watch.
Изменилось только содержимое регистра с адресом 02h (выделилось красным цветом).
Смотрим в распечатку области оперативной памяти: по этому адресу расположен счетчик команд (регистр PCL) и его содержимое изменилось с 00h на 01h, то есть, произошел переход рабочей точки на первую команду программы, а предшествующая (нулевая) команда исполнилась.
Далее я не буду "заострять" Ваше внимание на содержимом регистра PCL, так как ничего особо сложного в динамике его работы нет: его содержимое определяет адрес команды в памяти программ, предназначенной к исполнению при следующем "скачке" рабочей точки программы (или адрес команды в памяти программ, на которой "стоит" рабочая точка).
Необходимо только отметить, что регистр PCL задействуется всегда (при выполнении любой программы), и его содержимое, при исполнении программы, окрашивается в красный цвет (сами ответьте на вопрос: почему?).
Итак, исполнилась команда goto START.
Смотрим в распечатку команд: команда goto выполняется за 2 м.ц. (можете вызвать секундомер и проверить) и, при этом, никаких операций с содержимым регистров не производится (эта команда не меняет их содержимого и на состояние флагов не влияет).
Поэтому, неудивительно, что, кроме изменения показаний счетчика команд, никаких изменений, в открытых нами окнах, не произошло.
Выполняем команду clrf IntCon, нажимая F7.
Рабочая точка программы "перескочила" на команду clrwdt и содержимое регистра с адресом 03h окрасилось в красный цвет (изменение с 18h на 1Ch).
Смотрим в распечатку области оперативной памяти: этот адрес имеет регистр STATUS.
Смотрим в распечатку команд: при исполнении команды clrf должен установиться флаг нулевого результата (Z), что и произошло.
Проверяем: 18h=0001 1000, 1Ch=0001 1100. Все правильно - 2-й бит (или бит № 2 - как угодно) регистра STATUS (Z) установился в 1.
Почему содержимое регистра IntCon (адрес 0Bh) не окрасилось в красный цвет, ведь с ним производилась операция?
Ответ: по умолчанию MPLAB выставил в этом регистре 0, а при сбросе нуля в ноль получается ноль (подтверждение ранее установленного состояния), то есть, изменения содержимого регистра IntCon не произошло (а, значит, и выделения красным цветом не будет), не смотря на то, что с содержимым этого регистра была произведена операция.
Нажимая на F7, выполняем команду clrwdt.
Ничего не изменилось, кроме показания счетчика команд, так как эта команда не производит операций с регистрами, а биты регистра STATUS TO и PD (см. распечатку команд) уже были установлены ранее.
Выполняем команду (далее это словосочетание означает нажатие на F7) bsf Status,RP0.
Содержимое регистра STATUS изменилось с 1Ch (0001 1100) на 3Ch (0011 1100).
Все правильно: 5-й бит регистра STATUS (выбор банка) установился в 1 (выбор 1-го банка).
Выполняем команду movlw .65
Теперь "покраснела" строка в окне Watch, то есть, произошла запись числа .65 в аккумулятор (регистр W).
Переводим десятичное число .65 в 16-ричное и получаем 41h.
Сверяемся: это число и записалось (правда синтаксис немного другой, но это "что в лоб, что по лбу - все едино").
Выполняем команду movwf TrisB.
"Покраснело" содержимое регистра TrisB и в него, из регистра W, скопировалось (именно скопировалось, а не вырезалось - см. регистр W) число 41h.
Переводим число 41h в бинарную форму. Получается 0100 0001.
Все правильно: нулевой и шестой биты установлены в 1 (выводы RB0 и RB6 работают на вход, остальные - на выход).
Следующие 2 команды выполняются аналогично, только число .143 копируется из регистра W в регистр OptionR.
Проанализируйте правильность выполнения этих команд самостоятельно.
Также самостоятельно проверьте установку 5-го бита регистра STATUS в 0 (переход в нулевой банк) при исполнении команды bcf Status,RP0.
Проверка исполнения следующих 4-х команд также не должна вызвать у Вас затруднений.
В этом случае, константы, через регистр W закладываются не в регистры специального назначения, а в регистры общего назначения (SecH и SecL), адреса которых (0Dh и 0Eh) Вы найдете в "шапке" программы.
При выполнении этой группы команд, Вы увидите, что содержимое регистра TMR0 (адрес 01h) изменится, и далее, по ходу выполнения программы, в нем будет что-то подсчитываться.
Хотя, по большому счету, нам абсолютно все-равно, что он там считает (TMR0 в программе не задействован), но, с познавательной точки зрения, разобраться с этим стоит.
Настройки TMR0 производятся битами регистра OPTION.
В него мы записали число .143 = 1000 1111.
5-й бит этого регистра установлен в 0, следовательно, к входу TMR0 подключился внутренний тактовый сигнал (CLKOUT), то есть, TMR0 будет считать количество машинных циклов.
Задержка счета (относительно начала исполнения программы) объясняется тем, что, с момента записи данных в TMR0, счет начинается не сразу после записи.
На работу программы cus это никоим образом не влияет, но если кому-то не нравится лишняя точка отвлечения внимания, то можно отключить от входа TMR0 внутренний тактовый сигнал и подключить его (вход TMR0) к внешнему тактовому сигналу (к выводу RA4/TOCKI), который (сигнал) отсутствует.
После этого, в регистре TMR0 зафиксируются нули, и его содержимое, по ходу исполнения программы, меняться не будет (можете проверить).
Для того чтобы это сделать, нужно в команде movlw .143 константу .143 заменить на константу .175
Почему .175? Ответьте на этот вопрос сами.
Если Вы решили внести в текст программы это изменение, то, естественно, после внесения изменения, необходимо произвести ассемблирование и снова исполнить программу так, как описано выше, а еще лучше - произвести ассемблирование, после чего закрыть проект с сохранением изменений, затем вновь открыть проект, в который были внесены изменения и только после этого снова исполнить программу так, как описано выше.
Именно в последнем случае Вы увидите в регистре TMR0 нули, а в первом случае в нем может зафиксироваться число, отличное от нуля (результат предшествующего "гуляния" рабочей точки по программе, который не сбросился в ноль).
Такого рода "хождения по мукам" полезны тем, что помогают понять, что "чистой" настройкой по умолчанию является настройка на момент открытия проекта, а не настройка по сбросу программы на начало после того, как рабочая точка программы "погуляла" по программе.
Далее находится команда btfsc PortB,0.
Команда btfsc является командой ветвления, следовательно, нужно определиться, по какому из двух сценариев исполнения программы двигаться далее.
Так как этой командой опрашивается бит регистра порта В, то нужно посмотреть, в каком состоянии находится опрашиваемый бит регистра PortB перед исполнением команды btfsc PortB,0.
Для этого нужно открыть окно SFR, найти в нем строку состояния регистра PortB и посмотреть состояние нулевого бита.
В данном случае, он установлен в 0 (настройка по умолчанию, функции стимула не использовались).
После этого окно SFR можно закрыть (а можно и оставить - кому как нравится).
Вывод: при исполнении команды btfsc PortB,0, ухода в "вечное кольцо" ПП START не произойдет и, после выполнения команды btfsc PortB,0, будет исполняться команда btfss PortB,6.
При отслеживании правильности исполнения программы, программист может проверить как правильность исполнения этого сценария, так и правильность исполнения другого сценария.
В идеале, нужно проверить оба.
Предположим, что необходимо проверить правильность исполнения указанного выше сценария, то есть, ноль в нулевом бите регистра PortB нас устраивает.
Выполняем команду btfsc PortB,0.
В соответствии со сказанным выше, рабочая точка программы переместилась на команду btfss PortB,6
Смотрим в окно RAM: изменений нет (кроме PCL) так как значение нулевого бита считывалось, а не записывалось..
Перед выполнением команды ветвления btfss PortB,6, необходимо произвести проверку, аналогичную описанной выше (для команды btfsc PortB,0), в результате проведения которой выясняется: при текущем значении 6-го бита регистра PortB равном нулю, после исполнения команды btfss PortB,6, будет исполнена команда goto PRD и рабочая точка программы уйдет в "вечное кольцо" ПП START.
Предположим, что отслеживание выполнения этого сценария предполагается отложить "на потом", а сейчас необходимо отследить выполнение другого сценария ("программа исполняется далее").
Для этого прибегаем к описанной выше "уловке" (раз уж я изначально "привязался" к этому слову, то пусть это так и будет называться): меняем goto PRD на nop.
Так как в текст программы внесены изменения, то его нужно проассемблировать и выполнить программу с самого начала и до команды btfss PortB,6.
Выполняем команду btfss PortB,6.
Смотрим в окно RAM: картина такая же, как и при выполнении команды btfsc PortB,0 (и по той же причине).
Выполняем команду nop.
В окне RAM опять ничего не меняется (кроме PCL), так как эта команда не производит никаких действий.
Итак, рабочая точка программы "вышла на оперативный простор" и стоит на команде bcf PortB,2.
Выполняем эту команду.
В окне RAM опять ничего не изменилось (кроме PCL).
Почему, ведь команда bcf PortB,2 является активной командой?
Объяснение: по умолчанию 2-й бит регистра PortB установлен в 0. Откройте окно SFR и убедитесь в этом (подтверждение ранее установленного состояния).
Примечание: для того чтобы проверить состояние того или иного бита регистра PortB (и вообще, любого регистра), на первых порах, лучше "заглядывать" в окно SFR, так как его содержимое представлено там в удобной для восприятия форме (бинарной).
По мере набора опыта и навыков, можно вообще отказаться от "услуг" окна SFR и всю информацию подобного рода "извлекать" из таблицы окна RAM.
И в самом деле, в последнем случае, перед исполнением команды bcf PortB,2, нужно просто посмотреть, какое число установилось по адресу 06h (регистр PortB), перевести его из 16-ричной формы исчисления в бинарную и посмотреть на значение интересующего бита.
В этом случае, необходимость в открытии окна SFR отпадает.
Выполняем следующие 3 команды nop.
В окне RAM опять не произойдет никаких изменений (кроме PCL).
Рабочая точка программы "встала" на команду movlw .85.
Отследите самостоятельно выполнение этой команды и следующей за ней команды (movwf Sec) по образу и подобию приведенного выше отслеживания аналогичных команд (команды записи константы в регистр) и убедитесь, что в регистр Sec из регистра W скопировалось число .85 (55h).
Итак, рабочая точка программы стоит на первой команде циклической ПП задержки PAUSE_1 (clrwdt).
Ранее мы уже отслеживали выполнение этой команды, так что сделайте это самостоятельно.
Рабочая точка "встала" на команду decfsz Sec,F.
На момент ее исполнения, в регистре Sec (адрес 0Ch) находится число .85 (55h). Посмотрите в окно RAM и убедитесь в этом.
Выполняем команду decfsz Sec,F.
Смотрим в окно RAM. Изменение имеется: содержимое регистра Sec уменьшилось с 55h до 54h, то есть, на единицу (произошел декремент) в полном соответствии с алгоритмом выполненной команды.
Так как число 54h (результат операции, сохраненный в том же регистре Sec) отлично от нуля, то следующей исполняемой командой должна быть команда goto PAUSE_1.
Проверяем: и в самом деле, рабочая точка программы стоит именно на этой команде.
Выполняем команду goto PAUSE_1.
В окне RAM никаких изменений (кроме PCL) не происходит (аналогично выполнению команды goto START).
Смотрим в текстовый редактор: произошел безусловный переход рабочей точки программы с команды goto PAUSE_1 на ПП PAUSE_1 (на первую команду этой ПП - clrwdt).
Снова выполняем команду clrwdt.
Снова выполняем команду decfsz Sec,F.
Смотрим в окно RAM: содержимое регистра Sec еще раз уменьшилось на 1 (с 54h до 53h).
Число 53h не равно 0, значит далее, снова, будет осуществлен безусловный переход на ПП PAUSE_1 и так - до тех пор, пока регистр Sec не очистится (00h).
Таким образом, рабочая точка программы "закольцевалась" в ПП PAUSE_1.
Если пытаться "добраться" до конца такой "закольцовки" (00h по адресу 0Ch) пошагово, то это займет достаточно много времени (особенно, если счетчик - многоразрядный).
Есть 2 способа уменьшения этого времени: либо назначить точкой остановки команду bsf PortB,2 и "дойти" до этой точки в "автомате", либо поступить следующим образом: нажать на кнопку F7 и не отпускать ее, одновременно контролируя содержимое регистра Sec в окне RAM.
Вы увидите, что содержимое этого регистра достаточно быстро будет уменьшаться.
При приближении к нулю (например, где-то в районе чисел 01h - 04h), нужно остановить счет, отжав кнопку F7, и "добраться" до 00h пошагово исполняя программу при помощи все той же кнопки F7.
После выполнения команды последнего декремента (переход от 01h к 00h), рабочая точка программы перейдет на команду bsf PortB,2, что соответствует алгоритму работы команды decfsz.
Примечание: при отслеживании работы программы, не нужно проверять соответствие фактических и требуемых значений временных параметров программы, так как оно проверяется при отладке программы.
Главное при отслеживании работы программы - "протащить" рабочую точку программы по выбранному сценарию работы программы (а, в идеале, поочередно - по всем сценариям) до конца полного цикла программы (для различных сценариев, полные циклы программы могут быть различны по объему входящих в эти сценарии команд), с одновременным контролем соответствия фактического и задуманного алгоритмов исполнения программы.
Обнаружение такого несоответствия, очень часто, означает наличие ошибки функционального характера.
Итак, рабочая точка программы находится на команде bsf PortB,2.
Не буду повторять, практически, одно и то же, так как группы команд формирования отрицательного и положительного полупериодов идентичны и отследить выполнение группы команд формирования положительного полупериода Вы можете самостоятельно.
После формирования времени положительного полупериода, рабочая точка программы установилась на команде decfsz SecL,F.
Посмотрите в окно RAM.
На момент ее исполнения, регистр Sec очищен (в нем - 00h), а в регистры SecH (адрес 0Dh) и SecL (адрес 0Eh) ранее заложены константы .15 (0Fh) и .255 (FFh) соответственно.
Выполняем команду decfsz SecL,F.
Смотрим в окно RAM.
Содержимое регистра младшего разряда SecL уменьшилось с FFh (.255) до FEh (.254), то есть на 1, что соответствует алгоритму работы команды decfsz (естественно, с учетом сохранения результата операции в этом же регистре).
Результат выполнения операции (FEh) не равен нулю, следовательно, следующей исполняемой командой будет команда goto CYCLE.
Выполняем команду goto CYCLE, после чего рабочая точка программы "встает" на первую команду ПП CYCLE (btfsc PortB,0), что соответствует задуманному.
И в самом деле, после сформирования первого периода тонального сигнала, сразу же должно начаться формирование следующего периода и т.д. - до истечения 3-хсекундного интервала времени.
Таким образом, речь идет о "закольцовке" рабочей точки программы в группе команд между командой btfsc PortB,0 и командой goto CYCLE.
Вопрос: как отследить такую протяженную по времени "закольцовку"?
Ответ:
В этом случае, проверка правильности исполнения алгоритма работы программы сводится к проверке результата очищения регистра старшего разряда счетчика, ведь именно после его очищения должен выполниться сценарий работы программы - "программа исполняется далее".
Так как, в этом случае, речь идет о достаточно большом интервале времени, то лучше всего произвести отслеживание работы 2-хразрядного счетчика в "автомате".
Во всех остальных случаях (при пошаговом исполнении программы с кратковременными нажатиями F7 и при пошаговом исполнении программы с долговременным нажатием на F7), такого рода отслеживание займет много времени.
Итак, убеждаемся, что декремент старшего разряда счетчика происходит (ранее мы убедились, что декремент младшего разряда счетчика происходит).
Для этого необходимо назначить точку остановки на следующей после decfsz SecH,F команде (вспоминаем, что команда исполняется после того, как рабочая точка программы "перескочит" с нее на следующую команду), то есть, на команде goto CYCLE.
Итак, назначаем точкой остановки команду goto CYCLE.
Сбрасываем программу на начало.
Для наблюдения за процессом счета можно вызвать секундомер.
Щелкаем по кнопке с зеленым секундомером ("автомат") и дожидаемся конца процесса счета (придется подождать достаточно продолжительное время).
Через 175,71 мс. счет закончится и рабочая точка программы установится на команде goto CYCLE, следовательно, команда decfsz SecH,F будет исполнена.
Смотрим в окно RAM.
Содержимое регистра SecL (младший разряд счетчика, адрес 0Eh) очистится (00h), а содержимое регистра SecH (старший разряд счетчика, адрес 0Dh) уменьшится на 1 с 0Fh (.15) до 0Eh (14.), что соответствует алгоритму работы 2-хразрядного вычитающего счетчика (в чем и требовалось убедиться).
Можно, после этого, еще раз запустить "автомат": содержимое старшего разряда счетчика еще раз уменьшится на 1 (с 0Eh до 0Dh), а потом еще и еще..., пока счетчик старшего разряда не очистится, но это займет много времени, да и особой надобности в этом нет, так как достаточно и одного декремента содержимого счетчика старшего разряда, чтобы убедиться, что 2-хразрядный счетчик работает так, как задумано.
Вопрос: каким образом, после того, как мы убедились, что содержимое регистра SecH декрементируется, "проскочить" команды 2-хразрядного счетчика и выйти на сценарий "программа исполняется далее", не тратя массу времени на очистку содержимого регистра старшего разряда SecH?
Ответ: нужно сделать то, что мы уже делали ранее - заменить следующую после decfsz SecH,F команду (goto CYCLE) на команду nop.
Делаем это, предварительно убрав рабочую точку программы с команды goto CYCLE (проще всего это сделать, сбросив программу на начало).
В текст программы внесены изменения, следовательно, ассемблируем текст программы.
Теперь требуется удалить старую точку остановки и назначить новую.
Удалять старую точку остановки не нужно, так как она уже удалена при ассемблировании (при ассемблировании все точки остановки удаляются), значит нужно только назначить новую точку остановки на команде bcf PortB,2.
Делаем это.
Запускаем "автомат" и дожидаемся окончания процесса исполнения программы.
После окончания этого процесса, рабочая точка программы установится на команду bcf PortB,2.
Таким образом, мы "проскочили" 2-хразрядный счетчик, убедившись, что он работает, и перешли к сценарию "программа исполняется далее".
Примечание: если, после этих манипуляций, Вы посмотрите на показания секундомера и в окно RAM, то увидите ту же самую картину, что и в случае первого декремента содержимого SecH (175,71 мс. и 0Eh по адресу 0Dh), то есть, в этом случае, SecH не очищен (и это - естественно, так как при помощи одного декремента SecH очистить нельзя).
Тем не менее, хотя и программа, формально, не исполнена до конца (00h в SecH), мы можем, как говориться, "с чистой совестью" двигаться дальше, так как убедились, что счетчик работает по задуманному алгоритму, и, по этой причине, получили гарантию "нормальной" его работы после замены NOPа на "штатную" команду goto CYCLE.
Если у Вас есть время и терпение, то Вы можете, в познавательных целях, добиться фактического исполнения программы, очистив регистр SecH указанным выше способом, выйдя, тем самым, на сценарий "программа исполняется далее" (рекомендую).
Естественно, в этом случае, команду goto CYCLE менять на команду nop не нужно.
Итак, мы находимся на команде bcf PortB,2.
Выполняем ее.
Смотрим в окно RAM.
2-й бит регистра PortB изменил свое состояние с 1 на 0 (содержимое PortB изменилось с 04h на 00h), что соответствует алгоритму исполненной команды.
Рабочая точка программы перешла на первую команду ("врезку") ПП PRD - clrwdt.
Выполняем команду clrwdt.
Рабочая точка программы установилась на команде ветвления btfss PortB,0, следовательно, необходимо определиться: по какому из двух сценариев направить отслеживание выполнения программы?
Предположим, необходимо отследить выполнение сценария "программа исполняется далее".
Смотрим в окно RAM (или в окно SFR).
В регистре PortB все биты установлены в 0 (в том числе и интересующий нас нулевой бит).
Следовательно, по логике команды btfss, следующей должна выполниться команда goto PRD, после чего рабочая точка должна закольцеваться в ПП PRD, а это нас не устраивает.
Что делать?
Правильно, меняем goto PRD на все тот же "легендарный" nop.
Если отслеживается первая проверка, то со второй проверкой "возиться" нет смысла, так как она в точности такая же.
Следовательно, вторую проверку можно заблокировать (исключить из отслеживания), поставив перед обеими ее командами точки с запятой.
В тексте программы произошли изменения, значит ассемблируем текст программы (при этом, ранее установленная нами, точка остановки на команде bcf PortB,2 снимается).
Устанавливаем программу на начало.
Назначаем точку остановки на команде btfss PortB,0, запускаем "автомат" и ждем окончания процесса.
По его окончании, рабочая точка программы установилась на команде btfss PortB,0.
Выполняем ее.
Выполняем следующую команду (nop).
Рабочая точка программы установилась на команде goto START, минуя команды второй проверки, так как они заблокированы.
Итак, мы "протащили" рабочую точку программы по выбранному сценарию ее работы (самому "протяженному", в смысле количества команд).
Если выполнить команду goto START, то рабочая точка программы "уйдет" на новый полный цикл программы (в пределах данного сценария программы).
Из рассмотренного выше можно сделать следующие выводы:
Понятие "полный цикл программы" есть общее понятие и как-бы включает в себя несколько полных циклов программы (по количеству возможных сценариев ее исполнения).
Если это понятие необходимо "привязать" к конкретике (то есть, например, ответить на вопрос: каков полный цикл программы в машинных циклах?), то нужно формулировать это понятие следующим образом: "полный цикл программы для конкретного сценария ее исполнения", что вполне закономерно, так как различные сценарии ее иполнения будут содержать различное количество команд.
Отслеживание выполнения программы (для выбранного ее сценария) можно производить не детально (а можно и детально - кому как нравится), а в "формализованном" виде.
Это означает, что, если имеются несколько одинаковых или аналогичных групп команд, то достаточно отследить выполнение одной из них, а другие "проскочить" (или в пошаговом режиме, или в "автомате", назначив точку остановки после этой "другой" группы команд или вообще "выключить" эти команды, поставив перед ними точки с запятыми).
Конечно, все это достаточно условно и зависит от квалификации программиста.
И в самом деле, если программист вставил в текст программы подпрограмму, с которой он много раз работал и которую много раз проверял в работе, то какой смысл тратить время еще на одну ее проверку?
Например: группы команд формирования отрицательного и положительного полупериодов (в программе cus) идентичны по принципу своей работы и, в принципе, можно отследить только выполнение команд формирования отрицательного полупериода, а группу команд формирования положительного полупериода "проскочить", но для этого нужно быть полностью уверенным в том, что в этой группе команд нет ошибок, то есть, опять же, такого рода упрощение работы по отслеживанию "упирается" в квалификацию и опыт программиста.
Именно по этой причине, опытный программист может отследить выполнение различных сценариев работы программы "с реактивным свистом", а не имеющий такого опыта, может потратить на это дело уйму времени.
Все это - "дело наживное" и, по мере набора "ума-разума", процесс отслеживания будет занимать все меньше и меньше времени. Побольше "разборов полетов" и все будет ОК.
Еще раз обращаю Ваше внимание на разницу между отслеживанием и отладкой программы.
Отслеживание выполнения программы первично, и оно необходимо для проверки соответствия фактических алгоритмов ее исполнения задуманным, что автоматически означает проверку программы на предмет наличия ошибок функционального характера, которые, естественно, нужно устранить.
Отладка программы вторична и производится после отслеживания ее выполнения (а какой смысл производить отладку, если есть вероятность того, что в тексте программы имеются ошибки?).
Проще говоря, слову "отслеживание" можно поставить в соответствие слово "грубо", а слову "отладка" - слово "точно".
Почему в тексте "Самоучителя..." первой описывается отладка, а только затем - отслеживание?
Дело в том, что такой порядок обучения, с моей точки зрения, более рационален в смысле доходчивости.
Итак, вернемся к процессу отслеживания работы программы cus.
Мы отследили только один сценарий ее работы. Переходим к другим.
Если Вы внимательно читали то, что написано, то Вы должны были сделать примерно такой вывод: сценарии "порождаются" командами ветвления и по количеству этих команд можно составить представление об общем количестве сценариев исполнения программы.
По ходу отслеживания основного сценария, мы "навставляли" в текст программы NOPы, поэтому, совместим приятное с полезным: и сценарии отследим, и последовательно избавимся от "инородных" NOPов (последовательно вернем текст программы в "рабочее состояние").
Начнем с конца текста программы (так удобнее - подумайте, почему?).
Снимаем "блокировку" с команд второй проверки (убираем точки с запятыми), убираем nop (после команды btfss PortB,0) и возвращаем на свое "штатное" место команду goto PRD.
Это означает, что рабочая точка, по причине, изложенной выше, должна "закольцеваться" в ПП PRD.
Проверяем это.
В текст программы внесены изменения, значит нужно его проассемблировать.
Сбрасываем программу на начало.
Назначаем точкой остановки команду btfss PortB,0.
Запускаем "автомат" и ждем конца отработки.
Рабочая точка программы "встала" на назначенную точку остановки.
Выполняем команду btfss PortB,0. В соответствии с логикой команды btfss, рабочая точка программы переместится на команду goto PRD.
Выполняем команду goto PRD.
Произойдет безусловный переход на первую команду ПП PRD ("врезку" clrwdt).
А теперь понажимайте клавишу F7 "от души".
Произошла "закольцовка" (уход в "вечное кольцо") рабочей точки программы в ПП PRD, и пока нулевой бит регистра PortB будет равен 0, она так и будет там "крутиться", в чем и требовалось убедиться.
Проверяем следующий сценарий.
Убираем nop (после команды decfsz SecH,F) и возвращаем на свое "штатное" место команду goto CYCLE.
Ассемблируем, устанавливаем программу на начало, назначаем точку остановки на команде decfsz SecH,F.
Запускаем "автомат" и ждем окончания процесса.
Рабочая точка "встала" на команду decfsz SecH,F.
Выполняем ее и следующую за ней команду goto CYCLE.
Рабочая точка программы переместилась на первую команду ПП CYCLE (btfsc PortB,0), и она будет "крутиться" по кольцу (от btfsc PortB,0 и до goto CYCLE) до тех пор, пока регистр SecH не очистится, в чем и требовалось убедиться.
Проверяем следующий сценарий.
Убираем nop (после команды btfss PortB,6) и возвращаем на свое "штатное" место команду goto PRD.
Ассемблируем, устанавливаем программу на начало, назначаем точку остановки на команде btfss PortB,6.
Запускаем "автомат" и ждем окончания процесса.
Рабочая точка "встала" на команду btfss PortB,6.
Выполняем ее и следующую за ней команду goto PRD.
Рабочая точка программы переместилась на первую команду ПП PRD (clrwdt) и "закольцевалась" в ней (проверьте), в чем и требовалось убедиться.
Итак, остался последний сценарий.
Заменяем команду btfsc PortB,0 на команду btfss PortB,0 (еще одна "уловка" - берите на заметку).
Предлагаю Вам самостоятельно разобраться в сути произведенной замены.
Ассемблируем, устанавливаем программу на начало, назначаем точку остановки на команде btfss PortB,0.
Запускаем "автомат" и ждем окончания процесса.
Рабочая точка "встала" на команду btfss PortB,0.
Выполняем ее и следующую за ней команду goto START.
Рабочая точка программы переместилась на первую команду ПП START (clrf IntCon) и, далее, "закольцовывается" в ней (проверьте), в чем и требовалось убедиться.
Заменяем команду btfss PortB,0 на "штатную" команду btfsc PortB,0.
Ассемблируем текст программы.
Итог: все сценарии работы программы cus отслежены.
Примечание: при отслеживании последних сценариев работы программы cus, точки остановки, конечно же, можно назначать и непосредственно на командах переходов.
Переходите к следующему разделу.


Kea_31

11. Прерывания. Стек. Пример разработки программы с уходом в прерывания.

Пояснения: описываемые на моем сайте устройства и программы под них, не следует рассматривать как предложение создать такие устройства (хотя, это и вполне можно сделать, симитировав внешние сигналы при помощи кнопок или тумблеров), хотя бы по той простой причине, что не все "имеют под рукой" аппаратуру, к которой эти устройства должны подключаться.
Основная цель "разбора полетов" с привлечением этих устройств и программ - обучающая.
Применяемая терминология есть "продукт моей фантазии" ("закольцовка", "вечное кольцо", "уловка", "основное тело" программы, "врезка", "шапка" программы, "зона" действия прерываний и т.д.).
А что остается делать, если, фактически, стандартная терминология либо отсутствует, либо трактуется неоднозначно, либо она настолько неудобна и длинна, что, если ей пользоваться, то можно банально запутаться.
Можете придумать свою терминологию, это дело второе. Главное - понимать суть происходящих процессов.
Разбираем технологию конструирования следующего устройства и программы под него.
Рассмотрим такую жизненную ситуацию (из практики моей работы): за большие деньги закуплены импортные ретрансляторы Vertex-7000VXR (стоимость каждого - порядка 45000 руб.).
Выясняется, что они однонаправленные, то есть, "гонят информацию" только в одну сторону (с одной частоты на другую, а "обратного хода" нет), а необходим двунаправленный ретранслятор.
Если создавать такой ретранслятор из двух однонаправленных, то, кроме "вбухивания" в каждый такой ретранслятор больших денег, возникает куча технических и организационных проблем, связанных с большими трудозатратами.
Задача: перевести однонаправленный ретранслятор Vertex-7000VXR в двунаправленный режим работы, с минимальными материальными затратами (себестоимость устройства - не более 200 руб.) и без потери качества ретрансляций.
Начинаем "раскрутку", то есть, "борьбу" с неопределенностями.
Сначала нужно "привязаться" к аппаратуре.
В ретрансляторе Vertex-7000VXR не нужно организовывать переключение на передачу при наличии в приемном тракте несущей ретранслируемого радиосигнала, так как это делает сам ретранслятор.
Таким образом, задача сводится только к изменению раскладки частот: при ретрансляции в одну сторону частоты должны быть ПРМ - X, ПРД - Y, а при ретрансляции в другую сторону - наоборот, то есть, ПРМ - Y, ПРД - X.
Следовательно, речь идет о так называемом сканировании частот, то есть, о периодической смене раскладки частот, с остановкой их смены (остановкой сканирования), при появлении несущей ретранслируемого радиосигнала, на время наличия этой несущей.
Это должно выглядеть так ("грубо" формулируем алгоритм работы программы): при отсутствии несущей, разрабатываемое сканирующее устройство должно поочередно и последовательно переключать направления ретрансляции, то есть, указанные выше раскладки частот.
При этом, каждая из этих раскладок частот должна фиксироваться в течении некоторого оптимального времени (слишком быстро или медленно менять их нельзя).
При появлении несущей на любой из этих раскладок частот (естественно, в приемном тракте), сканирование должно быть остановлено на время наличия несущей и возобновлено при ее пропадании.
Если несущая появляется в приемном тракте, настроенном на частоту X, то ретрансляция происходит в одном направлении, а если она появляется в приемном тракте, настроенном на частоту Y, то ретрансляция происходит в другом направлении.
Сам процесс ретрансляции нас "не волнует", так как этим "занимается" сам ретранслятор.
Ретранслятор Vertex-7000VXR имеет на задней стенке разьем, с выводов которого можно переключать раскладки частот (конкретные раскладки частот нужно заранее программировать), вывод сигнала управления шумоподавителя (несущая есть/нет = 0/1 соответственно), вывод +13,8v с блока питания ретранслятора и корпус.
Выход, с которого снимается управляющий сигнал "несущая есть/нет" - с открытым коллектором.
Входы переключения частот расчитаны на стандартные 5-вольтовые уровни (преобразователей уровней не нужно).
Таким образом, для подключения сканера к ретранслятору, имеются все необходимые цепи, компактно выведенные на разьем.
Теперь можно перейти к составлению блок - схемы программы.
Как было указано выше, при наличии несущей, сканирование должно быть остановлено.
Следовательно, речь идет о необходимости ухода рабочей точки программы (на время наличия несущей) в "вечное кольцо", технология которого подробно описана ранее.
Конечно, можно составить программу и на этом принципе, но зачем повторяться?
Есть еще один способ такого рода локализации границ перемещения рабочей точки программы - уход рабочей точки программы в прерывание и ее "закольцовка" в подпрограмме (подпрограммах) прерывания.
Хотя, в конечном итоге, в подпрограммах прерывания, она уходит в то же самое "вечное кольцо", и, для данного устройства, программа без использования прерываний оказывается даже проще, чем программа с их использованием, но, с обучающей точки зрения, разбор программы, с уходом рабочей точки программы в подпрограммы прерывания, необходим.
Сделаем так: сначала разберемся с прерываниями и стеком, затем "разложим на молекулы" программу с уходом в прерывания и, после этого, я покажу Вам конструкцию той же самой программы, но без ухода в прерывания.
Итак, чтобы двигаться дальше, нужно понять: что такое прерывания?
Чтобы не повторяться, отсылаю Вас к 2-му разделу (см. регистр INTCON).
К информации, имеющейся там, можно добавить следующее:
по большому счету, в прерывания можно вообще не уходить (вообще их не использовать), так как все то, что можно сделать в подпрограммах прерываний, можно сделать и в "основном теле" программы, организовав периодическую (каждый полный цикл программы) проверку (опрос) клавиатуры (проверка значений управляющих внешних сигналов: вспомните проверки программы cus, это и есть проверки клавиатуры).
Различие состоит в том, что, при уходе в прерывания, такого рода проверку делать не нужно.
Определимся с терминологией: при организации прерываний, рабочая часть программы включает в себя все подпрограммы программы, включая и ПП прерываний.
Чтобы, для удобства объяснения, разделить программу на 2 части, условимся называть группу команд и/или группу подпрограмм прерываний как "ПП прерываний", а все остальное - "основным телом" программы.
Клавиатурой может быть любое из внешних устройств, формирующих один или несколько внешних (по отношению к ПИКу) управляющих сигналов.
Технология ухода в ПП прерывания следующая: например, мы решили "прерываться" по входу INT (есть и другие источники прерываний - см. распечатку регистра INTCON).
В "основном теле" программы обязательно нужно выделить группу команд, во время исполнения которых, при прохождении активного фронта управляющего сигнала по входу INT, могут происходить прерывания.
Сверху (по тексту "основного тела" программы), обычно, в начале ПП START (в подготовительных операциях), вставляется команда запрета всех прерываний clrf Intcon.
Это объясняется тем, что "уход" в прерывания будет происходить позднее и, если изначально их не запретить, то этот "уход" может быть преждевременным, чего допустить нельзя.
Далее программа исполняется до начала "зоны" действия прерываний.
"Зона" прерываний это группа команд "основного тела" программы, ограниченная "сверху" командой разрешения прерываний, а "снизу" - командой запрета прерываний.
Таким образом, в начале этой "зоны", действие прерываний по входу INT разрешается, после чего программа исполняется далее в режиме ожидания прерывания, а, в ее конце, прерывания опять запрещаются и программа исполняется далее.
При прохождении рабочей точкой программы команд "зоны" действия прерываний, могут наступить 2 события: если прерывания нет, то программа исполняется обычным образом (в "основном теле" программы), а если произошло прерывание (прошел активный фронт по входу INT), то происходит переход рабочей точки программы, из "зоны" действия прерываний "основного тела" программы, в ПП прерываний INT (после нее могут следовать и другие ПП, входящие в состав ПП прерываний) по стеку.
Это означает, что в стек записывается адрес следующей (после команды, во время исполнения которой произошло прерывание) команды, для обеспечения последующего возврата рабочей точки программы, при выходе из ПП прерывания, именно на эту "следующую" команду.
Например, представьте себе, что "зона" действия прерываний содержит 3 команды: A, B и С (сверху вниз по тексту программы).
Пусть активный фронт сигнала прерывания по входу INT поступил во время исполнения команды А. Происходит переход рабочей точки программы на первую команду ПП прерывания INT.
При этом, адрес команды В автоматически запишется в стек и будет в нем находиться, пока не исполнится последняя команда ПП прерываний, которой всегда должна быть команда retfie.
По этой команде осуществляется возврат по стеку, что означает то, что из него извлекается адрес команды В и возврат рабочей точки программы произойдет именно на эту команду, после чего программа исполняется далее уже в "основном теле" программы.
Если прерывание наступит во время исполнения команды В, то в стек запишется адрес команды С.
Учтите, что программист, не задействуя в тексте программы команды (команд) условного перехода, никак не может "повлиять" на стек и, по этой причине, он является как бы "темной лошадкой", за которой можно только наблюдать, то есть, только констатировать факты либо выполнения команды (команд) условного перехода, либо возврата (возвратов).
Стек "подчиняется" только командам переходов и возвратов, а программиста "он просто ставит перед фактом события", за что его многие "недолюбливают" и стремятся обойтись без его привлечения (использование одних только команд безусловных переходов).
Однако, и в первую очередь это касается сложных программ, существует множество случев, когда задействование стека позволяет составить программу с гораздо меньшим количеством команд, чем в случаях, когда он не применяется.
Такими вещами лучше заниматься после набора приличного опыта и навыков.
И еще: при написании ПП прерываний, в любое их место (до команды retfie) должна быть, в обязательном порядке, вставлена команда сброса флага прерывания по выбранному входу (входам).
В нашем случае, используется вход INT и команда должна выглядеть так: bcf Intcon,1.
С точки зрения "наведения порядка" в тексте, ее лучше не "вклинивать" между командами подпрограмм, а распологать либо, непосредственно перед командой retfie, либо недалеко от нее, либо где-то в начале ПП INT.
При отсутствии этой команды (bcf Intcon,1), рабочая точка программы, "уйдя" первый раз из "основного тела" программы в ПП прерываний, будет "метаться" между ПП прерываний и "основным телом" программы (пока питание будет включено) при фактическом отсутствии прерываний (программа зависнет), то есть, если после возврата из ПП прерываний, флаг прерывания находится в поднятом состоянии (а это так и будет, так как, при исполнении ПП прерываний, он автоматически поднимается), то это "воспринимается" ПИКом как управляющий сигнал "ухода" в ПП прерываний.
Кроме того, специалисты Микрочипа рекомендуют, в начале ПП прерываний, сохранить содержимое регистров STATUS и W в специально созданных под это дело регистрах общего назначения (стандартные названия: Stat_Temp и W_Temp, но они могут быть и другими - по желанию программиста), а в конце ПП прерываний - восстановить их содержимое.
Дело в том, что, по ходу исполнения ПП прерываний, содержимое регистров STATUS и W может измениться и, после возврата из ПП прерываний, это содержимое может негативным образом повлиять на исполнение программы в "основном теле" программы.
Итог: для ухода в прерывания необходимо: в "шапке" программы определить вектор прерывания, определить активный фронт прерываний (OPTION), в "основном теле" программы назначить источник и "зону" действия прерываний (INTCON), в начале ПП прерываний сохранить, а в конце ПП прерываний (до команды retfie) - восстановить содержимое регистров STATUS и W, сбросить флаг прерывания по выбранному входу (входам) прерывания, а в самом конце ПП прерываний "вставить" команду retfie.
Все это Вы увидите в разбираемой программе.
И еще одно замечание: уход в прерывания выгодно производить, если существует некая "объемная" группа команд, которую нужно функционально "вычленить" из рабочей части программы и которая должна исполняться при поступлении активного перепада внешнего управляющего сигнала.
В этом случае, такому сигналу "присваивается статус" сигнала прерывания, а, указанная выше, группа команд "трансформируется" в ПП прерываний, "вырезается" из рабочей части программы и располагается в тексте программы "особняком", что упрощает работу с текстом программы и делает его более комфортным для восприятия.
Кроме того, при работе со стандартной клавиатурой (например, от кнопочного телефонного аппарата), уход в прерывания применяется если не всегда, то очень часто (случай организации прерываний по изменению уровня сигнала на входах RB4 - RB7).
То же самое можно сделать и в случае необходимости выполнения какой-то функционально обособленной группы команд (какого-то действия) при переполнении TMR0 в результате подсчета определенного количества активных перепадов поступающих от внешнего источника, при достижении которого, например, включается исполнительное устройство, подается сигнал тревоги, данные выводятся на индикацию и т.д.
Прерывания по окончании записи в EEPROM-память происходят в конце процедуры записи данных в EEPROM-память.
Итак, выше перечислены источники прерываний для PIC16F84A (см. распечатку регистра INTCON).
В более "навороченных" микроконтроллерах их может быть больше.
Вернемся к нашему устройству.
Итак, "уходим" в прерывания.
Сразу возникает вопрос: что там "будем делать"?
Исходя из того, что рабочая точка программы должна находиться в ПП прерываний все время, пока приемник фиксирует наличие несущей (управляющий сигнал "несущая есть"), вывод напрашивается сам по себе: в ПП прерываний, нужно "закольцевать" рабочую точку программы в "вечном кольце", то есть, сделать то, чем мы уже занимались раньше.
При этом нужно организовать проверку типа "несущая есть/нет" (опрос клавиатуры) с "уходом" рабочей точки программы в циклическую ПП задержки (при наличии несущей), основой которой является многоразрядный счетчик и выходом из нее при "пропадании" несущей.
Ранее мы рассматривали работу вычитающего счетчика. Теперь же, в обучающих целях, сделаем этот счетчик комбинированным, то есть, вычитающе - суммирующим (забегаю вперед: счетчик - двухразрядный).
Естественно, ПП прерываний нужно соответствующим образом оформить с учетом сказанного выше.
В "основном теле" программы, сначала, нужно произвести подготовительные операции (ПП START).
Необходимо чем-то переключать направления ретрансляции, следовательно, нужно создать нечто типа "триггера", который управлял бы сменой раскладок частот при сканировании.
Состояние этого "триггера" должно периодически опрашиваться и, в зависимости от него, должен быть осуществлен переход на ту или иную раскладку частот.
Естественно, он должен менять свое состояние каждый раз после завершения сканирования той или иной раскладки частот.
Определимся, что опрос состояния "триггера" будет производиться сразу после ПП START, а изменение его состояния - в конце "основного тела" программы (а можно и наоборот).
Между этим опросом и этим изменением, в простейшем случае, нужно сформировать "зону" действия прерываний (об этом - см. выше), но одного этого оказывается недостаточным по следующей причине.
Раскладки частот переключаются в синтезаторе частот ретранслятора Vertex-7000VXR, который, также, как и любой синтезатор частот, является инерционным устройством, то есть, переход с одной раскладки частот на другую, занимает некоторое время, и в это время возникают переходные помехи, наличие которых обусловлено переходными процессами в кольце фазовой автоподстройки частоты синтезатора частот.
Эти помехи могут "трансформироваться" в "паразитный" сигнал управления "несущая есть", что приведет к уходу в прерывания без фактического наличия несущей.
Для того, чтобы избавиться от этой неприятности, нужно "переждать", пока не закончатся переходные процессы в кольце фазовой автоподстройки частоты синтезатора частот.
Таким образом, речь идет о том, что после выбора направления ретрансляции (переключения раскладок частот), необходимо "закольцевать" рабочую точку программы, на некоторое время, в ПП задержки для формирования защитного интервала времени, и только после этого формировать "зону" действия прерываний.
"Зона" действия прерываний также должна иметь некоторую протяженность во времени, следовательно, в этой "зоне" должна располагаться ПП задержки (счетчик) с калиброванным временем задержки (это и есть время сканирования).
Если в течение этого времени поступит активный перепад с вывода RB0/INT (несущая есть), то процесс сканирования прекратится, так как рабочая точка программы "уйдет" в ПП прерываний и будет "крутиться" там, и возобновится при ее выходе из ПП прерываний (несущей нет).
Все, осталось только назначить названия для подпрограмм и можно составлять блок - схему программы.
Что из этого получилось, Вы можете узнать, скачав файл блок - схемы с расширением .DOC

Файл блок - схемы программы Вы можете скачать: здесь.

Принципиальная схема устройства получается настолько простой, что можно обойтись и без составлении блок - схемы устройства, а сразу приступить к составлению его принципиальной схемы.
Определяемся с задействованными выводами портов:
Порт А не задействуем, порт В задействуем.
Входом прерывания INT назначаем вывод RB0, следовательно, вывод RB0/INT (INT означает то, что вывод RB0 задействуется для внешнего прерывания INT) необходимо настроить на работу "на вход".
Примечание: никакой другой вывод порта В (и порта А тоже, если он задействуется) входом внешнего прерывания INT назначать нельзя, так как этот вид прерываний "жестко привязан" именно к выводу RB0.
Так как управляющий сигнал "несущая есть/нет" поступает на вывод RB0/INT с выхода каскада с открытым коллектором, то необходимо включить подтягивающие резисторы.
В этом случае, подтягивающий резистор вывода RB0/INT будет являться коллекторной нагрузкой каскада с открытым коллектором.
Экспериментально проверяем состояние каскада с открытым коллектором: при отсутствии несущей транзистор закрыт, а при ее наличии - открыт, что, с учетом подтягивающего резистора вывода RB0/INT, соответствует единичному уровню, если несущей нет, и нулевому уровню, если она есть.
Берем это на заметку, при составлении программы это потребуется.
Выходом управления переключением направлений ретрансляций (раскладки частот) можно назначить любой из оставшихся выводов порта В.
Пусть им будет, например, вывод RB2. Естественно, этот вывод нужно настроить на работу "на выход".
Остальная часть принципиальной схемы, практически, такая же, как и для ранее рассмотренного устройства формирования тонального вызова с частотой 1450 Гц.

Файл принципиальной схемы устройства Вы можете скачать: здесь.

Пояснение: в ретрансляторе Vertex-7000VXR, для дистанционного переключения пар частот (одна частота - приемная, другая - передающая), на разъем выведено 4 цепи (переключение пар частот производится в инвертированном весовом параллельном коде).
Таким образом, можно осуществить переключения между 16-ю парами частот, но нам необходимо переключаться только между двумя парами частот, следовательно, можно задействовать под это дело только один младший разряд (подключить его к выводу RB2), а остальные 3 разряда запараллелить и "подать" на них единицу, то есть, подключить их к 4-му выводу PIC16F84A.
В этом случае, переключения будут производиться между 1-м и 2-м каналами (1-й и 2-й парами частот).
1-му каналу соответствует единица на выводе RB2, а 2-му каналу - ноль на выводе RB2.
В принципиальной схеме, c целью упрощения ее восприятия, я не показал описанных выше соединений (но они должны быть).
Просто имейте это ввиду.
Принципиальную схему устройства вообще можно предельно упростить, если запитаться от цепи +5v ретранслятора (это же относится и к рассмотренному выше устройству формирования тонального вызова).
В этом случае, стабилизатор на 142ЕН5 можно убрать, но придется "лезть" в ретранслятор и делать отвод от +5v.
А теперь посчитайте себестоимость этого устройства (25-штырьковый разъем - компьютерный). В 200 руб. уложились (по ценам на комплектующие, которые мне известны).
Приступаем к составлению текста программы под это устройство.
Технология составления текста программы "с чистого листа" описана выше и повторяться я не буду.
Сразу разбираем конечный вариант программы.

Файл текста программы Retr_1.asm Вы можете скачать: здесь.

Создайте проект с названием Retr_1, загрузите в него этот файл, проассемблируйте текст программы и убедитесь в отсутствии ошибок.
В первую очередь, обращаю Ваше внимание на то, что, при использовании прерываний, директива назначения вектора прерываний org 4 должна находиться в "шапке" программы ниже (по тексту) команды безусловного перехода goto START.
Только в этом случае первая команда ПП прерываний будет располагаться по 4-му адресу счетчика команд PC (там, где ей и положено быть: определено разработчиками ПИКов).
Откройте окно ROM и убедитесь в этом.
Обратите внимание также на то, что рабочая часть программы, в обязательном порядке, должна начинаться с ПП прерываний и "основное тело" программы должно быть расположено только после команды retfie.
Только в этом случае, переход по вектору прерывания (адрес в PC - четверка) произойдет именно на первую команду ПП прерываний.
Вопросы может также вызвать и ПП INT.
И в самом деле, команд переходов на эту ПП в тексте программы нет, тогда зачем вводить в текст программы это название ПП?
Ответ: название INT можно считать и названием подпрограммы, и названием метки, это кому как нравится.
Давайте, например, назовем ее меткой.
Естественно, название метки INT никоим образом не "привязано" к тому, что выбрано прерывание по входу INT (RB0/INT) или к тому, что именно с метки с таким названием должна начинаться подпрограмма (подпрограммы) прерываний.
Название этой метки, так же, как и название любой другой метки, может быть любым.
Чтобы понять, зачем она нужна, необходимо уяснить следующее:
При работе с текстом программы (отслеживание, отладка) проблем с переходом в "основное тело" программы не возникает: сбрасываем программу на начало, нажимаем F7 и мы - на первой команде ПП START.
А как попасть на первую команду ПП прерываний?
Без "уловок" здесь не обойтись.
Самая простая из них: на первой команде ПП прерываний ставится метка (в нашем случае - INT) для обеспечения перехода на эту команду.
В "шапку" программы, сразу же после команды goto START, вставляется команда goto INT (в тексте программы ее нет, но при отслеживании и отладке программы ее нужно вставить).
А теперь все очень просто: если ставим точку с запятой (блокируем команду) перед командой goto INT (перед командой goto START точки с запятой нет) - попадаем на первую команду ПП START, а если снимаем точку с запятой с команды goto INT и ставим точку с запятой перед командой goto START - попадаем на первую команду ПП прерываний (movwf W_Temp).
Вот для этого и нужна метка с названием INT (или с другим названием).
В текст программы я ее ввел для наглядности: чтобы как-то обозначить команду, с которой начинается ПП прерываний.
Команду goto INT можете прямо сейчас вставить в "шапку" программы, в дальнейшем, при отслеживании и отладке, она потребуется, только заблокируйте ее точкой с запятой.
Перед "прошивкой", то есть, после того, как программа полностью отработана, эту метку (а также и команду goto, которая обращается к этой метке) из текста программы можно удалить. Это никак не повлияет на работу устройства, обслуживаемого этой программой.
Разбираем текст программы
 Эту "разборку" я буду проводить не так детально, как ранее, с учетом того, что предыдущий материал Вами усвоен.
Принцип конструкции "шапки" - стандартный, повторяться не буду, остановлюсь только на особенностях.
Три пары регистров с названием Sec - элементы трех 2-хразрядных счетчиков.
Регистры со стандартными названиями W_Temp и Stat_Temp это регистры оперативной памяти, использующиеся для сохранения содержимого регистров W и Status на время исполнения ПП прерываний.
На регистре с названием Trigg "собран" триггер со счетным входом.
В "определении места размещения результатов операций" дополнительно "прописан" регистр W, так как в тексте программы используются команды, результат исполнения которых необходимо сохранить в регистре W.
В конце "шапки", в соответствии с тем, что было сказано выше, располагается директива MPLAB - org 4.
Выполнение любой программы с уходом в прерывания (в том числе и этой) начинается с запрета всех прерываний (clrf IntCon).
Команду сброса WDT - clrwdt (в программе он задействован - см. биты конфигурации) можно вставить в любое место подготовительных операций.
Причина такой "демократии" заключается в том, что время срабатывания WDT (минимальное - 18мс.) с запасом перекрывает практически любой интервал времени выполнения команд подготовительных операций.
Команда clrwdt, в начале исполнения программы, может быть даже и не нужна, если последующие команды clrwdt обеспечивают перезапуск RC-одновибратора сторожевого таймера до окончания формирования его импульса, но, по принципу "кашу маслом не испортишь", все-таки, лучше "вставить" ее в ПП START.
Далее, также, как и в программе cus, устанавливаются направления работы выводов порта В: RB0/INT - "на вход", остальные (в том числе и интересующий нас вывод RB2) - "на выход" (0000 0001).
Подтягивающие резисторы порта В включены.
Собственно говоря, нужно подключить только один подтягивающий резистор к выводу RB0/INT, но так как все они могут быть включены или отключены только "оптом", значит и подключаем их все.
В этом же регистре OptionR устанавливаем 6-й бит (выбор активного фронта прерывания INT) в ноль, то есть, прерывание, в соответствии с задуманным, будет осуществляться по заднему фронту (по перепаду от 1 к 0).
Остальные биты регистра OptionR, кроме 6-го и 7-го нас не интересуют и в них можно выставить любой уровень.
Для удобства "привяжемся" к нулевым уровням 6-го и 7-го битов, поэтому выставляем во всех битах регистра OptionR нули, что соответствует значению байта равному нулю.
Смотрим в блок - схему программы.
Теперь нужно опросить состояние переключателя направлений ретрансляции типа триггера со счетным входом.
Создаем этот триггер (или переключатель - кому как нравится).
Особенности конструирования подобных устройств (имеющих 2 состояния по выходу: 0 или 1) заключаются в следующем:
Естественно, под это дело сначала назначается ("прописывается") регистр общего назначения.
В нашем случае это регистр Trigg (см. "шапку" программы).
Все регистры общего назначения 8-битные и их содержимое, по этой причине, может принять значение любого числа от .0 до .255.
Такое большое количество состояний нам ни к чему, ведь нужно только два из них.
Любой из 8-ми разрядов указанного выше числа может находиться в одном из двух состояний (0 или 1), но чаще всего эти состояния будут меняться (в результате исполнения операций инкремента или декремента) в младшем разряде.
Последовательно инкрементируя или декрементируя содержимое регистра Trigg, значение бита его младшего разряда будет меняться с 0 на 1 или наоборот при каждом инкременте или декременте (вот Вам и триггер со счетным входом).
В этом случае, значения битов оставшихся 7-ми разрядов нас не интересуют, и опрос состояния регистра Trigg можно производить по принципу: если в нем четное число (нулевой бит =0), то программа исполняется по одному сценарию, а если нечетное (нулевой бит =1), то программа исполняется по другому сценарию.
Применительно к нашему случаю, это означает, что при равенстве нулевого бита регистра Trigg нулю, ретрансляция будет происходить в одну сторону, а при его равенстве единице - в другую сторону.
Разбираем ПП TRIGGER.
Первая ее команда проверяет состояние нулевого бита регистра Trigg (btfsc Trigg,0).
btfsc - команда ветвления, следовательно, имеются 2 сценария дальнейшей работы программы.
Разница заключается в том, что, в случае исполнения одного сценария, в регистр PortB закладывается константа .251 (1111 1011), то есть, на выводе RB2 устанавливается 0 (одно направление ретрансляции), а в случае исполнения другого сценария, в регистр PortB закладывается константа .255 (1111 1111), то есть, на выводе RB2 устанавливается 1 (другое направление ретрансляции).
Так как на линейном участке программы (а мы сейчас как раз с таким и работаем) рабочая точка программы движется только сверху вниз (по тексту программы) и последовательной закладки обеих констант в регистр PortB допустить нельзя (должна закладываться либо одна, либо другая), то для того, чтобы это обеспечить, необходимо команду movlw .255 пометить меткой (Metka148), и безусловный переход осуществить на эту метку.
Почему именно команду movlw .255, а не команду movlw .251?
А потому, что с команды movlw .251 начинается сценарий типа "программа исполняется далее", для перехода на который, в данном случае, команды перехода не требуется.
С другой стороны, на команду movlw .255 никаким образом, кроме как с использованием команды перехода, перейти нельзя.
Кроме того, необходимо, чтобы, после выполнения любого из этих сценариев, рабочая точка программы попала на одну и ту же команду продолжения программы: в нашем случае это команда movlw .197 (раздвоение на сценарии с последующим их слиянием).
Если, после исполнения команд закладки в регистр PortB константы .255, рабочая точка программы, естественным образом, сразу же, попадает на команду movlw .197, то, после исполнения команд закладки в регистр PortB константы .251, рабочая точка программы, сразу же, попасть на команду movlw .197 не "может", так как для этого придется выполнить команды закладки в регистр PortB константы .255, чего допустить нельзя (какой смысл закладывать .251, если, в конечном итоге, все равно заложится .255?).
Значит, если в регистр PortB закладывается константа .251, то команды закладки константы .255 в регистр PortB нужно каким-то образом "обойти".
Проще всего это сделать, пометив команду movlw .197 еще одной меткой (Metka_1) с осуществлением безусловного перехода на нее после исполнения команд закладки константы .251 в регистр PortB.
Посмотрите, как это сделано в программе Retr_1.
После того, как направление ретрансляции выбрано (произошло переключение раскладок частот), необходимо сформировать защитный интервал времени величиной примерно 50 - 150 мс. (что это такое - см.выше).
Для такой классной машины, как Vertex-7000VXR, хватит и 60 мс. (даже с запасом). На эту цифру и нужно ориентироваться при отладке программы.
Таким образом, речь идет о ПП задержки, следовательно, предварительно (то есть, до начала ее исполнения), нужно заложить, в задействованные под нее регистры общего назначения (SecL_1 и SecH_1), константы.
Калиброванную задержку величиной порядка (не обязательно абсолютно точно) 60 мс. можно сформировать и одним разрядом счетчика, но, с точки зрения универсальности устройства (оно может быть подключено и к аппаратуре с большей инерционностью синтезатора частот), лучше сделать его 2-разрядным.
Для разнообразия, младший разряд счетчика сделаем вычитающим, а старший разряд - суммирующим.
Собственно говоря, добавить что-то к тому, что было сказано ранее, при "разборках" с 2-хразрядным вычитающим счетчиком (программа cus), нечего, за исключением напоминания о том, что в разряде суммирующего счетчика направление приближения к нулю противоположно направлению приближения к нулю в разряде вычитающего счетчика.
Если, например, нужно увеличить время задержки, то величину константы вычитающего разряда счетчика необходимо увеличить, а величину константы суммирующего разряда счетчика - уменьшить.
Что касается конкретных значений констант, то они подобраны из расчета формирования защитного интервала времени величиной 60 мс.
Попробуйте, для тренировки, пересчитать величину защитного интервала времени с учетом этих констант (методика этого пересчета была приведена ранее) и с учетом конструкции счетчика. В итоге Вы должны получить значение, близкое к 60 мс.
В соответствии с блок - схемой программы, теперь необходимо сформировать интервал времени сканирования.
Это делается, в своем принципе, точно также, как и в случае формирования защитного интервала времени.
В обучающих целях, я задействовал в счетчике интервала времени сканирования отдельную пару регистров общего назначения SecH и SecL.
Таким образом, в "основном теле" программы, с которым мы сейчас работаем, задействованы 4 регистра общего назначения: SecH_1 и SecL_1 - при формировании защитного интервала времени, а также SecH и SecL - при формировании интервала времени сканирования.
А теперь посмотрим на содержимое регистров SecH_1 и SecL_1 на момент начала формирования интервала времени сканирования.
Оба они очищены (в них записаны нули) и далее, по ходу выполнения программы (в том числе и в ПП прерываний), они не используются.
Вывод: их можно использовать повторно - при формировании интервала времени сканирования, то есть, можно обойтись без регистров SecH и SecL, заменив их, соответственно, на SecH_1 и SecL_1.
Верно также и обратное утверждение: можно оставить только SecH и SecL, а SecH_1 и SecL_1 убрать ("вычеркнуть" из "шапки" программы).
В пределах одного полного цикла программы, работа этой пары регистров, в данном случае, будет выглядеть так: после закладки в них констант защитного интервала времени в начале этого интервала и очистки этих регистров в конце формирования защитного интервала времени, в них снова будут заложены константы, но только не защитного интервала времени, а интервала времени сканирования, после чего данные регистры, в конце формирования интервала времени сканирования, снова очистятся.
В соответствии с этим, конечно же, в текст программы можно (и нужно, если эту программу предполагается использовать по своему прямому назначению) внести изменения, но, так как все познается в сравнении, я посчитал целесообразным, в обучающих целях, разобрать именно этот (4 регистра вместо 2-х) не слишом рациональный вариант построения программы.
Вывод из всего этого следующий: если имеются однотипные счетчики (ПП задержки с калиброванным временем задержки), то, в случае, если на момент начала работы одного счетчика, другой счетчик свою работу закончил (задействованные под него регистры общего назначения очистились), можно использовать в этих счетчиках одну и ту же группу регистров общего назначения (по количеству разрядов счетчиков), не назначая для каждого из этих счетчиков отдельных групп регистров общего назначения.
Забегая вперед, расскажу про случай, когда одну и ту же группу регистров нельзя использовать в двух счетчиках.
В ПП прерываний имеется, аналогичный описанным выше, 2-хразрядный счетчик на регистрах SecH_2 и SecL_2.
Пары регистров SecH_2, SecL_2 и SecH_1, Sec_L1 можно объединить в одну пару, так как на момент начала работы одного счетчика (и в течение всей его работы), содержимое другого счетчика очищено и наоборот.
А вот регистры SecH_2, Sec_L2 и SecH, SecL объединить нельзя, так как, на момент начала работы счетчика SecH_2, SecL_2, содержимое счетчика SecH, SecL не будет очищено (вероятность очищения есть, но она очень мала).
Если это сделать, то нормальная работа устройства будет нарушена.
Если изложенное выше, сейчас, не понятно, то вернитесь к этому после ознакомления с работой ПП прерываний.
Изначально, рекомендую Вам, под каждый счетчик назначать свою отдельную группу регистров общего назначения (как это сделано в программе Retr_1), а указанными выше объединениями заняться позднее - по мере набора опыта и навыков.
Теперь вернемся к счетчику интервала времени сканирования.
Его конструкция и принцип работы должны быть, сейчас, Вам понятны.
В соответствии с сформулированным ранее алгоритмом работы устройства, во время формирования интервала времени сканирования, рабочая точка программы должна (при наличии активного перепада на выводе RB0) уйти в ПП прерываний.
Следовательно, "зона" действия прерываний должна включать в себя именно этот интервал времени.
В начале этой "зоны" необходимо разрешить внешние прерывания INT (по выводу RB0/INT).
Разрешить их до прохождения команд (или между ними) закладки констант в регистры счетчика (SecH, Sec_L) можно, но, лично я, предпочитаю "привязываться", непосредственно, к началу счета.
Поэтому, команды закладки константы в регистр IntCon расположены именно в том месте, где Вы их видите (см. текст программы Retr_1).
В этом регистре, 4-й и 7-й биты установлены в 1. Это и есть разрешение прерываний по INT.
Общая особенность: при разрешении любого вида прерываний, бит глобального разрешения прерываний должен быть установлен в 1.
Если, например, прерывание INT разрешено (4-й бит = 1), а глобального разрешения прерываний нет (7-й бит = 0), то все прерывания будут запрещены (в том числе и "разрешенное" - по INT).
В конце "зоны" действия прерываний, прерывания нужно запретить.
Проще всего это сделать, запретив их все, командой clrf IntCon.
Собственно говоря, запретить все прерывания можно, установив один только 7-й бит регистра IntCon в 0, например, используя бит - ориентированную команду bcf IntCon,7, но зачем напрягаться (вспоминать номер бита глобального разрешения прерываний)?
В нашем случае, проще сбросить все биты в 0 и дело с концом.
Смотрим в блок - схему программы. Теперь нужно обеспечить предварительную подготовку смены раскладки частот для следующего "витка" полного цикла программы.
При этом, содержимое регистра Trigg можно и декрементировать, и инкрементировать.
Выбераем инкремент.
Так как, при последующих проверках (при следующих исполнении команд ПП Trigger, которые происходят на каждом "витке" полного цикла программы) производится проверка на четность (проверяется только значение младшего, то есть, нулевого, бита регистра Trigg), то нас совершенно "не волнует" числовое значение байта.
Оно может быть любым. Самое главное - ответ на вопрос: какое это число, четное (нулевой бит = 0) или нечетное (нулевой бит = 1)?
Естественно, от "витка" к "витку" полного цикла программы, будут происходить последовательные смены четного числа на нечетное (или наоборот, в зависимости от того, с какого из них начинать отсчет этих смен), следовательно, также последовательно будут осуществляться переходы с одной раскладки частот на другую, что и требуется.
После того, как произведен инкремент содержимого регистра Trigg (incf Trigg,F), можно выходить на новый полный цикл программы (goto START).
Обратите внимание на то, что применена команда incf, а не команда ветвления incfsz, так как, в данном случае, сценарий исполнения программы - единственный: "программа исполняется далее".
Итак, мы "прошли" по "основному телу" программы, исходя из предположения, что активных перепадов не поступало.
Теперь предположим, что на вывод RB0/INT, с внешнего устройства, поступил активный перепад (от 1 к 0).
Как только это событие наступает, происходит условный переход по стеку на вектор прерывания, то есть, рабочая точка программы "перескакивает" на первую команду ПП прерываний (на адрес 04h в счетчике команд PC).
В вершину стека, обычным образом, будет заложен адрес команды, следующей за командой, во время исполнения которой произошел уход в прерывание.
Далее программа будет исполняться в ПП прерываний до команды retfie включительно.
ПП прерываний можно рассматривать как некую "программу в программе".
Иногда даже количество команд ПП прерываний может в несколько раз превосходить количество команд в "основном теле" программы, и "все основные дела делаются" именно в них.
Теперь необходимо выполнить сформулированные выше условия бесконфликтной работы и взаимодействия ПП прерываний и "основного тела" программы.
В начале ПП прерываний необходимо сохранить содержимое 2-х регистров: Status и W. Зачем это нужно - см. выше.
Если есть такая необходимость (а она может быть), можно, дополнительно, сохранить содержимое и других "жизненноважных" регистров.
Критерий - следующий: при возврате из прерываний, содержимое регистров должно быть таковым, чтобы работа в "основном теле" программы продолжилась по запланированному программистом алгоритму, то есть, в содержимое "жизненноважных" регистров, на момент выхода из ПП прерываний, не должно быть внесено таких изменений, которые могли бы привести к потере работоспособности программы.
Если в ПП прерываний, содержимое этих регистров изменяется таким образом, то нужно сделать как раз то, о чем идет речь.
Специалистами Микрочипа рекомендуется (после ухода в прерывания) проделывать операции сохранения и восстановления содержимого 2-х указанных выше регистров.
В большинстве случаев, этого оказывается вполне достаточным.
Существует стандартная (рекомендованная) процедура этих операций, о смысле которой можно даже особо и не задумываться - просто вставить 3 команды этой процедуры (сохранение) в начало ПП прерываний, и 4 команды (восстановление) в конце (перед командой retfie) так, как это сделано в тексте программы Retr_1.
Естественно, регистры Stat_Temp и W_Temp нужно "прописать" в "шапке" программы.
Что при этом происходит, Вы узнаете из комментариев в тексте программы, так что любознательные могут с этим разобраться.
Эта рекомендованная процедура, в обучающих целях, вставлена в текст программы в полном своем объеме, хотя, в данном случае, содержимое регистра W можно не сохранять (а, соответственно, и не восстанавливать).
И в самом деле, при возврате из ПП прерываний в "основное тело" программы, число, находящееся в регистре W, ни в какой регистр не копируется вплоть до записи в него константы .1 (команда movlw .1 в "подготовительных операциях"), а, следовательно, и нет смысла сохранять содержимое регистра W при уходе в прерывания и восстанавливать его перед возвратом из прерывания.
Проще говоря, в нашем случае, какой бы величины число не находилось в регистре W, после возврата из прерывания, на работу программы оно не повлияет.
Так что, можно смело убирать и регистр W_Temp из "шапки" программы, и команды сохранения и восстановления содержимого регистра W из ПП прерываний.
Мало того, то же самое относится и к регистру Status.
И в самом деле, уход в прерывание происходит в нулевом банке, работа в ПП прерываний происходит в нулевом банке и работа в "основном теле" программы, после возврата из ПП прерываний (в пределах "зоны" действия прерываний), также происходит в нулевом банке, а флаги регистра Status в программе не задействованы (обращений к ним нет).
Таким образом, в данной программе вообще можно не применять указанные выше процедуры, но я Вам не советую убирать команды этих процедур "с глаз долой".
От того, что Вы их уберете из текста программы, в ее работе, практически, ничего не изменится (изменения, конечно, будут, но несущественные), но при этом Вы потеряете возможность визуального запоминания стандартной конструкции ПП прерываний.
Если, в данной программе, без этих процедур обойтись можно, то в других программах они могут, в обязательном порядке, потребоваться.
Смотрим в блок - схему программы.
Необходимо произвести проверку: несущая есть/нет?
Подобными делами мы уже занимались не один раз, так что повторяться не буду.
Если несущей нет, то осуществляется безусловный переход в ПП выхода из прерывания EndInt (о ней - позднее).
Если несущая есть, то необходимо каким-то образом задержать рабочую точку программы в ПП прерываний.
В простейшем случае, можно после команды goto EndInt "поставить" команду goto CYCLE, "закольцевав" рабочую точку программы в "вечном кольце" из 3-х команд (сверху - вниз: btfsc PortB,0, goto EndInt, goto CYCLE), но, в этом случае, проверки нулевого бита регистра PortB будут производиться так быстро и так часто, что это может отразиться на качестве работы ПП прерываний.
В этом случае, ПП прерываний будет реализовывать некое малоинерционное устройство, сильно подверженное действию коротких импульсных помех.
Чтобы увеличить помехоустойчивость этого устройства, нужно сделать его инерционным, то есть, ввести в его состав некий фильтр, который "отсеивал бы" значительную часть импульсных помех.
А если речь идет о инерционности, то это означает введение в состав "вечного кольца" подпрограммы задержки с калиброванным временем задержки (оно определяет инерционность).
Естественно, при таком раскладе, будет осуществляться калиброванная по времени задержка в пределах одного полного цикла ПП CYCLE, а подпрограмма задержки (вместе с командами закладки констант) будет являться "врезкой" в циклическую ПП CYCLE, увеличивающей время полного цикла ПП CYCLE.
Проще говоря, опрос нулевого бита регистра PortB будет производится не так часто и с цикличностью, определяемой временем задержки.
В программе Retr_1 время исполнения полного цикла ПП CYCLE составляет 5,62 мс.
Это мой субъективный выбор, можно установить и какое-то другое значение, соответствующим образом изменив значения констант.
Можно также упростить ПП CYCLE, заменив 2-хразрядный счетчик на 1-разрядный.
После того, как будет произведено отслеживание и отладка программы в том виде, в котором Вы ее видите, можете, для тренировки, сделать эту замену, а затем произвести соответствующую калибровку времени полного цикла ПП CYCLE.
Найдите в тексте программы подпрограмму CYCLE и посмотрите, как сказанное выше реализуется на практике.
ПП с такой конструкцией мы уже разбирали ранее и добавить к этому мне нечего.
При выходе из прерывания (ПП EndInt) нужно обязательно сбросить флаг прерывания по входу INT (bcf IntCon,1), восстановить содержимое "жизненноважных" регистров и исполнить команду возврата из прерывания по стеку retfie.
Например, если уход в прерывание произошел во время исполнения команды incfsz SecH,F, то, после выхода из ПП прерываний, рабочая точка программы "встанет" на следующую (после incfsz SecH,F) команду (goto PAUSE).
При отладке программы Вы увидите как, в этом случае, работает стек и сможете проконтролировать его содержимое.
Пояснение: в тексте программы Retr_1 Вы не найдете команд переходов на ПП TRIGGER (аналогия с ПП INT).
Это название я ввел в текст программы, для того, чтобы обозначить начало группы команд выбора направления ретрансляции (для того, чтобы Вы легче "ориентировались" в тексте программы).
Если Вы не испытываете трудностей при работе с текстом программы, то это название (TRIGGER) можно удалить: это никак не отразится на работе программы.
При работе с программой cus, сначала, была выполнена отладка, а затем - отслеживание.
При работе с программой Retr_1, поступим наоборот, то есть так, как и нужно работать с любой программой.
Отслеживание работы программы в пределах ее "основного тела"
 Подготовьтесь к отслеживанию, как это было описано при отслеживании работы программы cus.
Вставьте в "шапку" программы команду goto INT (об этом - см. выше).
Сначала работаем с "основным телом" программы.
Заблокируйте команду goto INT точкой с запятой, а команду goto START разблокируйте (если она была заблокирована).
Сначала "прогоним" рабочую точку программы по тому сценарию ее работы, который определяют настройки MPLAB по умолчанию.
В дальнейшем, рекомендую Вам начинать отслеживание работы любой программы именно с этого сценария.
Ассемблируем текст программы (так как были внесены изменения) и устанавливаем рабочую точку на начало.
Пошагово исполняем программу (F7).
Команды ПП START "проходим без осложнений" ("линейный" участок).
ПП START программы Retr_1 построена аналогично ПП START программы cus, поэтому останавливаться на ее отслеживании я не буду, сделайте это сами и убедитесь, что, по ходу ее исполнения, все прерывания запрещаются, вывод RB0 устанавливается на работу "на вход", вывод RB2 - "на выход", включаются подтягивающие резисторы порта В и вывод RB0/INT настроен на на прерывания по заднему фронту внешнего управляющего сигнала.
Первой командой ПП TRIGGER является бит - ориентированная команда ветвления btfsc Trigg,0.
Смотрим в окно RAM. По адресу 0Ch (адрес регистра Trigg в области оперативной памяти, "прописанный" в "шапке" программы), на момент исполнения этой команды, записано число 00h, следовательно, нулевой бит регистра Trigg установлен в 0 и, в соответствии с алгоритмом работы команды btfsc, следующей после нее должна исполниться первая, а затем и вторая команда закладки константы .251 в регистр PortB, что и имеет место быть (проверьте и убедитесь в этом).
"Стучим" по F7.
Доходим до команды goto Metka_1 и исполняем ее, осуществив безусловный переход на команду, помеченную меткой Metka_1 (movlw .197).
Исполняем далее процедуру закладки констант в регистры SecH_1 и SecL_1, команду "врезки" в ПП задержки PAUSE_D (clrwdt) и устанавливаем рабочую точку программы на команду decfsz SecL_1,F.
Далее, конечно же, можно "постукать" по клавише F7, чтобы выйти из кольца ПП PAUSE_D, но это займет много времени.
Чтобы быстро "проскочить" эту "закольцовку", лучше перейти на "автомат": назначаем точку остановки на команде movlw .245, щелкаем по зеленому светофору и ждем окончания процесса.
Рабочая точка встала на команду movlw .245.
Далее исполняем все последующие команды и устанавливаем рабочую точку программы на "врезку" ПП PAUSE (clrwdt).
По ходу этого исполнения убеждаемся, что в регистры SecH и SecL записались числа .245 (F5h) и .255 (FFh) соответственно, а в регистр IntCon записалось число .144 (90h/1001 0000).
ПП PAUSE, по своей конструкции, такая же, как и ПП PAUSE_D, значит назначаем точку остановки на команде clrf IntCon, включаем "автомат" и дожидаемся конца процесса.
Исполняем команду clrf IntCon и убеждаемся, что его содержимое изменилось с .144 на .00.
Запоминаем значение числа, записанного в регистр Trigg (адрес 0Ch в окне RAM), исполняем команду incf Trigg,F и оцениваем изменение, произошедшее с содержимым регистра Trigg.
Значение записанного в регистр Trigg числа должно увеличиться на 1.
Исполняем команду goto START.
Рабочая точка программы "встанет" на первую команду ПП START, то есть, произойдет ее переход на новый "виток" полного цикла "основного тела" программы, к чему мы и стремились.
Один сценарий работы программы отслежен, отслеживаем следующий сценарий.
Ищем первую, после команды clrf IntCon, команду ветвления.
Это команда btfsc Trigg,0.
Один сценарий ветвления этой команды мы уже отследили. Отслеживаем второй.
Вспоминаем про "уловки": заменяем в тексте программы команду btfsc Trigg,0 на команду btfss Trigg,0.
Ассемблируем текст программы (т.к. внесены изменения) и устанавливаем программу на начало.
По второму сценарию должен произойти безусловный переход на метку Metka148, поэтому назначаем точкой остановки команду movlw .255.
Запускаем "автомат" и ждем окончания процесса.
Рабочая точка программы "встала" на команду movlw .255.
Исполняем эту команду и следующую за ней команду movlw PortB. Убеждаемся, что во все биты регистра PortB записались единицы (число .255).
Далее этот сценарий отслеживается точно так же, как и первый сценарий.
Никаких других сценариев работы программы в "основном теле" программы больше нет, следовательно, произведено полное отслеживание.
При этом мы убедились в отсутствии функциональных ошибок при работе программы в обоих подрежимах сканирования.
Примечание: ранее говорилось, что количество сценариев работы программы зависит от количества команд ветвления.
В общем виде это верно, но теперь следует уточнить: каких именно команд ветвления?
В группе команд "основного тела" программы, кроме команды ветвления btfsc Trigg,0, Вы увидите еще 4 команды ветвления (decfsz, incfsz).
Формально, каждая из них "порождает" по 2 сценария, но эти сценарии второстепены по отношению к рассмотренным выше, так как они "прокручиваются" внутри подпрограмм задержки (PAUSE_D и PAUSE), а эти подпрограммы, при движении рабочей точки по полному циклу "основного тела" программы, исполняются всегда.
Таким образом, де-юре, эти команды ветвления являются сценарными, а, де-факто, сценарии их работы локализованы внутри "глобальных" сценариев и являются их частью.
Таким образом, достаточно отследить только "глобальные" сценарии, что автоматически позволяет отследить и все "локальные" сценарии.
Отслеживание работы программы в пределах ПП прерываний
Для того чтобы войти в ПП прерываний, необходимо в "шапке" программы снять блокировку с команды goto INT (убрать точку с запятой) и заблокировать команду goto START (поставить точку с запятой).
После этого необходимо проассемблировать текст программы и установить ее на начало, при этом рабочая точка программы "встанет" на команду goto INT.
Исполняем эту команду. Рабочая точка программы "встанет" на первую команду ПП прерываний (movwf W_Temp) и далее будет "скакать" по командам ПП прерываний.
После исполнения первых 3-х команд, содержимое регистров Status и W должно скопироваться в регистры Stat_Temp и W_Temp соответственно.
В данном случае, копироваться будут значения по умолчанию.
В регистре W, по умолчанию, установлено число 00h.
Так как в регистре W_Temp, по умолчанию, также установлено число 00h, то, при сохранении содержимого регистра W в регистре W_Temp, никаких изменений в окне RAM не произойдет (кроме PC).
В регистре Status, по умолчанию, установлено число 18h.
Проследите (в окнах Watch и RAM), как это число, сначала, запишется в регистр W (movf Status,W - переслать содержимое регистра Status в регистр W), а, затем, скопируется из регистра W в регистр Stat_Temp.
Перед исполнением команды ветвления btfsc PortB,0, необходимо посмотреть на состояние нулевого бита регистра PortB.
По умолчанию, он установлен в ноль, следовательно, рабочая точка программы, в соответствии с алгоритмом работы команды btfsc, перейдет на сценарий "программа исполняется далее", то есть, на команду movlw .250.
Убеждаемся в этом и, далее, исполняем все команды закладки констант в регистры SecH_2 и SecL_2.
Убеждаемся, что в регистры SecH_2 и SecL_2 из регистра W скопировались числа .250 и .120 соответственно.
Переходим на "врезку" ПП задержки PAUSE_2 (clrwdt).
Далее, отслеживание работы программы происходит аналогично отслеживанию работы ПП PAUSE или PAUSE_D.
Собственно говоря, работу ПП PAUSE_2 можно и не отслеживать, а пройти ее в "автомате", поставив точку остановки на первую команду ПП CYCLE (btfsc PortB,0). Так и делаем.
Выставляем точку остановки, щелкаем по кнопке с зеленым светофором и убеждаемся, что рабочая точка программы "встала" на команду btfsc PortB,0.
Это свидетельствует о том, что полный цикл этого сценария работы программы в ПП прерываний успешно пройден.
Не снимая установленную точку остановки, можно, для того чтобы в этом окончательно убедиться, еще раз (да и сколько угодно раз) щелкнуть по кнопке с зеленым светофором и удостовериться в том, что, после отработки "автомата", рабочая точка программы, пройдя полный цикл этого сценария, снова окажется на команде btfsc PortB,0, в чем и требовалось убедиться (при наличии несущей, рабочая точка программы должна "уйти" в "вечное кольцо" ПП CYCLE).
Один сценарий отслежен. Отслеживаем следующий.
Возвращаемся все к той же команде btfsc PortB,0.
Применяем "уловку", заменив эту команду на команду btfss PortB,0.
Производим ассемблирование текста программы и, по его окончании, устанавливаем программу на начало.
Так как должен произойти безусловный переход на первую команду ПП EndInt, устанавливаем точку остановки на команде bcf IntCon,1.
Запускаем "автомат" и ждем окончания его отработки.
Рабочая точка программы "встала" на команду bcf IntCon,1.
Исполняем ее и убеждаемся, что 1-й бит регистра IntCon установился в 0.
Далее исполняем стандартный "набор" из 4-х команд восстановления содержимого регистров Status и W (можно особо не "забивать себе голову" тем, что при этом происходит: если орфографических ошибок в этих командах нет, то все выполнится "в лучшем виде").
"Встаем" на команду retfie.
Исполняем команду retfie и, после этого, получаем предупреждение от MPLAB о том, что в стеке ничего нет и извлекать из него нечего.
Закрываем окно предупреждения, щелкнув по ОК, после чего рабочая точка программы устанавливается на начало (команда goto INT в "шапке" программы).
В чем дело, почему стек пустой?
Ответ: переход в ПП прерываний был осуществлен не из "зоны" действия прерываний "основного тела" программы, а при помощи "уловки", то есть, проще говоря, условного перехода не было, а, следовательно, и в стек ничего не записалось.
Так как, по ходу отслеживания, никаких команд условных переходов не исполнялось, а стек, по умолчанию, пустой, значит и к моменту исполнения команды retfie он также будет пуст, о чем мы и получили предупреждение.
А раз это так, то, при щелчке по ОК, MPLAB устанавливает рабочую точку программы на начало, как бы предлагая найти и устранить ошибку, а затем попробовать повторить отслеживание еще раз.
Таким образом, для "полноценного" отслеживания данного сценария работы ПП прерываний, необходимо "уйти" в прерывание из "зоны" действия прерываний "основного тела" программы.
Проще всего это сделать, не задействуя функции стимула, а при помощи очередной "уловки", смысл которой заключается в следующем:
В списке команд Вы не обнаружите команды условного перехода в ПП прерываний, в нем имеется только команда возврата из ПП прерываний (retfie).
Но это вовсе не означает, что "уход" в ПП прерываний происходит по какому-то неведомому указанию "святого духа".
Эта команда вполне конкретна и называется она call, просто она не вводится в текст программы (ведь неизвестно, во время исполнения какой команды "зоны" действия прерываний произойдет этот "уход"), а автоматически исполняется при поступлении активного перепада прерывания.
Таким образом, чтобы проверить "уход" в прерывание из "зоны" действия прерываний "основного тела" программы, необходимо просто - напросто вставить в любое место "зоны" действия прерываний команду call INT.
Для того, чтобы быстро "добраться" до этой команды (не ждать очищения разрядов счетчика), ее лучше поставить между командами clrwdt и decfsz SecL,F (ПП PAUSE).
Для работы в "основном теле" программы также необходимо заблокировать команду goto INT и разблокировать команду goto START.
Делаем это.
Ассемблируем текст программы и, после получения сообщения о безошибочном ассемблировании, устанавливаем программу на начало (goto START), назначаем точку остановки на команде call INT и запускаем "автомат".
Ждем его отработки, после чего рабочая точка программы устанавливается на команде call INT (то есть, таким образом, мы "добрались" до этой команды).
А теперь внимание - разбираемся со стеком.
Сначала нужно открыть специальное окно, в котором мы будем наблюдать содержимое стека.
Это делается так: в главном меню MPLAB щелкаем по слову Window и в выпадающем списке щелкаем по строке Stack.
Откроется окно Stack Window. В нем пока ничего нет (т.к. команд условных переходов не было).
Расположите это окно в любом удобном для Вас месте обычным способом.
Исполняем команду call INT.
Рабочая точка программы "уйдет" в ПП прерываний и "встанет" на первую их команду (movwf W_Temp).
А теперь посмотрите в окно стека. В нем появилась строка с адресом той команды, на которую, после возврата рабочей точки программы из ПП прерываний, она "встанет".
Этот адрес - 003Bh.
Откройте окно ROM и убедитесь, что команда с этим адресом (decfsz SecL,F) является следующей после команды, с которой произошел "уход" в прерывание (call INT).
При переходе в ПП прерываний по активному перепаду, происходит то же самое, только, в данном случае, положение команды call INT в тексте программы фиксировано, а при реальной работе устройства прерывание (автоматически формируемая микроконтроллером команда call INT) может наступить при исполнении любой из команд "зоны" действия прерываний.
Напоминаю Вам, что ранее мы отслеживали исполнение 2-го сценария работы ПП прерываний.
Еще раз "прогоним" по нему рабочую точку программы, но, на этот раз, мы вошли в ПП прерываний "как положено", то есть, из "основного тела" программы (в вершину стека заложен адрес возврата).
Так как, при первом "прогоне", мы уже отследили работу этого сценария и убедились, что все ОК, то сразу ставим точку остановки на команду retfie и запускаем "автомат". Рабочая точка прграммы "встала" на команду retfie.
Исполняем ее: вершина стека очистится (стек станет опять пустым), рабочая точка программы вернется в "основное тело" программы и установится на команде decfsz SecL,F, в чем и требовалось убедиться.
Итак, мы разобрались со всеми сценариями работы программы и убедились в отсутствии ошибок функционального характера, а заодно и заглянули в стек.
Программа отслежена.
Теперь нужно "навести в программе порядок", вернув ее к исходному состоянию (убрать все "уловки").
Примечание: Если активный перепад прерывания попадает в "зону" действия прерываний, то тут, как говорится, нет вопросов.
А если активный перепад прерывания (переход от 1 к 0) поступит в момент запрета прерываний? В этом случае произойдет уход в ПП прерываний или нет?
Ответ: если, после перехода от 1 к 0, нулевой уровень на управляющем входе прерывания фиксируется на такое длительное время, что он (нулевой уровень) присутствует на управляющем входе прерывания во время исполнения первой, после команды разрешения прерываний, команды, то прерывание произойдет.
Если формирование этого нулевого уровня и начнется, и закончится до исполнения команды разрешения прерываний, то прерывания не произойдет.
В первом случае, получается что-то типа задержки активного перепада с целью "втискивания" его в начало "зоны" действия прерываний, причем, этой процедурой "занимается" сам ПИК.
Если бы, в этом случае, указанное выше правило не соблюдалось, то "уход" в прерывания происходил бы нестабильно (через раз, два, ... и вообще - по всякому).
И еще одна немаловажная деталь: следует четко разграничивать процесс прерывания и процесс опроса клавиатуры.
В нашем случае, вывод RB0/INT задействован в обеих процессах.
Если речь идет о прерываниях, то, в рассматриваемом случае, имеет значение только активный фронт управляющего импульса прерывания (переход от 1 к 0) и то, что следует за ним до момента прохождения пассивного фронта управляющего импульса прерывания (переход от 0 к 1), то есть, время фиксации нулевого уровня, а если речь идет об опросе клавиатуры, то имеет значение любой уровень управляющего сигнала, зафиксировавшийся на выводе RB0/INT на момент опроса.
Таким образом, управляющий импульс, поступающий на вывод RB0/INT "рулит" сразу двумя процессами.
Вопрос: "Из чего исходить, определяя протяженность во времени "зоны" действия прерываний, а, проще говоря, из чего исходить, выбирая величину задержки ПП PAUSE?
Ответ: если задержку ПП PAUSE сделать слишком маленькой, то это отразится на качестве ретрансляции слабых радиосигналов корреспондентов из-за того, что, даже при очень кратковременных понижениях уровня несущей ниже порога срабатывания шумоподавителя, рабочая точка программы будет выходить из "зоны" действия прерываний.
Это будет приводить к тому, что следующая активация ретранслятора наступит только после того, как будет отсканирован другой канал, и рабочая точка программы снова вернется в "зону" прерываний, что, естественно, отражается на качестве связи, так как, во время этих "гуляний", ретранслятор, прежде чем снова переключиться в активное состояние, будет находиться в пассивном состоянии довольно-таки длительное время.
Увеличение величины задержки ПП PAUSE эквивалентно увеличению инерционности системы в том смысле, что увеличивается вероятность того, что, после подобного сбоя, рабочая точка программы, быстрее, чем в описанном выше случае, вернется в "зону" действия прерываний (сканирования другого канала не происходит), и из-за этого, время нахождения ретранслятора в пассивном состоянии существенно уменьшится.
Проще говоря, рабочая точка программы, в течение одного полного цикла "основного тела" программы, может несколько раз последовательно "уходить" и "возвращаться" в "зону" действия прерываний (в ПП PAUSE), и, в этом случае, тенденция такова, что, в среднем, количество таких "уходов" и "возвратов" прямо пропорционально зависит от величины задержки ПП PAUSE.
Отладка программы
 Настало время уточнить смысл этого словосочетания.
При отслеживании работы программы мы убедились в том, что программа работает в соответствии с задуманным алгоритмом ее работы.
При этом мы не обращали внимания на ее временные (ударение - на букве ы) характеристики.
Так как в большинстве программ имеются калиброванные (с той или иной точностью, это зависит от специфики разрабатываемого устройства) по времени задержки, то именно к коррекции величин этих задержек (под задуманные) и сводится процесс отладки программы.
Обращаю Ваше внимание, что отлаживаться может как "локальная" ПП задержки, так и более "навороченная" ПП задержки, имеющая внутри своего кольца одну или несколько "локальных" ПП задержек.
Примером "локальной" ПП задержки может послужить, например, ПП PAUSE_2 (см. текст программы Retr_1), а примером "навороченной" ПП задержки может послужить ПП CYCLE.
И ту, и другую можно отладить, то есть, изменить времязадающие константы (а при необходимости - добавить или удалить калибровочные NOPы) таким образом, чтобы полный цикл этих ПП был равен расчетному значению.
Для того чтобы это сделать без ошибок, как минимум, необходимо представлять себе работу программы на уровне, хотя бы, блок - схемы программы и принцип работы устройства, которое она обслуживает (естественно, при знании ассемблера).
В процессе отладки, программист должен уметь "вычленять" из текста программы оба указанных типа ПП задержек и четко представлять себе функции этих задержек в "общей картине" программы.
Только после этого имеется гарантия того, что он поставит точки остановки (определит границы отстраиваемого временного интервала) точно и без ошибок.
Разбираемся с задержками программы Retr_1.
Ранее было сформулировано требование к величине защитного интервала времени: он должен быть равен примерно 60 мс.
Определяемся с границами защитного интервала времени.
Очевидно, что он начинает формироваться после исполнения команды movwf PortB(таких команд две), как для 1-го, так и для 2-го сценария работы программы в "основном теле" программы.
Сразу "настораживаемся": два сценария могут исполняться за разное время, а нам, предположим, нужно сделать так, чтобы оба сценария выполнялись за одинаковое время (хотя, в данном случае разница в микросекунды особой роли не играет, но есть много случаев, когда наличие такой разницы не желательно или вообще не допустимо).
По этой причине производим оценку этой разницы.
Естественно, общие команды этих 2-х сценариев в расчет не принимаются.
Остается часть команд этих сценариев, где рабочая точка программы, в зависимости от сценария ее исполнения, движется по различным командам (различным направлениям).
Эта часть команд ограничена сверху (по тексту программы) командой ветвления btfsc Trigg,0, а снизу - командой movwf PortB (перед ней - команда movlw .255).
На этом участке команд, после команды btfsc Trigg,0, происходит "разветвление" сценариев, а "слияние" сценариев происходит на команде movlw .197.
Для того чтобы выровнять время исполнения этих сценариев, необходимо подсчитать количество машинных циклов (не команд, а именно машинных циклов), за которые они выполняются.
Если при этом выявятся различия, то необходимо произвести их выравнивание калибровочными NOPами.
Начинать эти подсчеты с команды btfsc Trigg,0 не имеет смысла, так как, при ее исполнении, переключения направлений ретрансляции не происходит (не с нее начинается формирование защитного интервала времени).
Оно происходит при исполнении команд movlw PortB (их две штуки - по количеству сценариев). С них и нужно начинать подсчет количества машинных циклов для различных сценариев работы.
Считаем количество машинных циклов от команды movwf PortB (от той, которая следует после команды movlw .251) и до команды movlw .197: movwf PortB (1 м.ц.) - goto Metka_1 (2 м.ц.). Итого: 3 м.ц.
Делаем то же самое от второй команды movwf PortB (от той, которая следует после команды movlw .255): movwf PortB (1 м.ц.). Итого: 1 м.ц.
Вывод: для уравнивания значений защитных интервалов времени обеих сценариев, необходимо, после команды movwf PortB (той, которая следует после команды movlw .255) "вставить" в текст программы 2 выравнивающих NOPа.
В тексте программы их нет по причине того, что "разнобой" в 2 м.ц. никакой "погоды не делает" но, при желании, Вы их можете в него "вставить".
Если Вы это сделаете, то оба сценария будут исполняться за одно и то же время.
По этой причине, в пределах указанной выше группы команд, достаточно произвести отладку только одного из 2-х сценариев работы программы (любого).
Проще всего обойтись без "уловок" и исполнить сценарий, определяемый настройками по умолчанию.
Этот пример с выравниванием времени исполнения сценариев приведен в чисто обучающих целях.
На практике, в других программах, такое выравнивание может быть необходимым (об этом пойдет речь в одном из следующих разделов), так что обратите на это внимание.
Например, если в измерительном приборе типа частотомера не выровнять сценарии, то его показания не будут стабильными даже при абсолютно стабильной измеряемой частоте.
После того, как мы убедились, что оба сценария будут исполняться за одно и то же время (если имеется такая необходимость), можно заняться калибровкой.
Если Вы добавили в текст программы NOPы, то нужно произвести ассемблирование.
Поставьте программу Retr_1 на начало (goto START) и, в пошаговом режиме (а можно и в "автомате"), "встаньте" на команду movwf PortB (на ту, которая находится после команды movlw .251).
Вызовите секундомер и сбросьте его на ноль.
Назначьте точку остановки на команде movlw .245 (конец формирования защитного интервала времени) и запустите "автомат".
После того, как он отработает, секундомер покажет ровно 60 мс.
Естественно, в этом случае, константы защитного интервала времени уже отстроены (.197 и .121).
Можете, для тренировки, поизменять их и посмотреть, что из этого получится.
Процесс отладки подобного рода ПП задержки уже был подробно расписан выше, так что повторяться не буду.
Замечание: фактически, формирование защитного интервала времени заканчивается не после выхода рабочей точки программы из ПП PAUSE_D, а после исполнения команды movwf IntCon, то есть, величина защитного интервала времени окажется на 6 мкс. (6 команд по одному м.ц.) больше (60 006 мкс.), что, при нашем допуске (вспомните - "примерно"), вполне приемлемо.
Отладка остальных ПП задержек - аналогична.
"Пройдитесь" по этим ПП задержек самостоятельно. После этого Вы должны получить следующие результаты:
- величина "зоны" разрешения прерываний: 11,28 мс. (границы: верхняя - movwf IntCon, нижняя - clrf IntCon).
В "основном теле" программы работа закончена, переходим в ПП прерываний (вспоминайте про "уловку" goto INT).
- величина времени одного цикла задержки рабочей точки программы в прерывании: 5,62 мс. (точка остановки - одна: btfsc PortB,0. Цикл - от этой точки остановки и, через один "виток" кольца, до нее же).
Примечание: собственно говоря, отладки, как таковой, и не было (константы не менялись), а была просто проверка величин задержек, которые уже были отлажены ранее.
Чтобы процесс отладки был "полноценным", для тренировки, можете изменить исходные требования к этим величинам и, руководствуясь принципом отладки, подробно описанным при отладке программы cus, произвести отладку программы Retr_1.
Итак, отладка закончена и HEX - файл программы Retr_1 можно загружать в программу, обслуживающую Ваш программатор, после чего эту программу можно "зашить" в ПИК с большой вероятностью того, что устройство будет работать именно так, как замыслено.
А теперь о программе, выполняющей то же самое, но без ухода в прерывания.

Текст программы для устройства сканирования без ухода в прерывания Retr_3.asm Вы можете скачать: здесь.

Сравните тексты программ Retr_1 и Retr_3.
Если Вы хорошо усвоили предыдущий материал, то текст программы Retr_3 , а также его отличия от текста программы Retr_1, не будут вызывать у Вас никаких затруднений.
Если это не так, то вернитесь назад и найдите там ответы на Ваши вопросы.
Программу Retr_3 можно использовать как некую "лакмусовую бумажку": пока Вам в ней что-то не понятно, не стоит переходить к изучению следующих разделов, а лучше разобраться с предыдущими.
В идеале, программа Retr_3 должна быть Вам понятна "от и до".
Переходите к следующему разделу.


Kea_32

12. Организация вычисляемого перехода. Работа с EEPROM
памятью данных.

Подводим промежуточный итог.
Разбирая программы Multi, cus, Retr_1, Retr_2, я стремился объяснить Вам фундаментальные основы программирования микроконтроллеров (любых).
На данный момент Вы должны четко и ясно представлять себе общие принципы составления текста программы, движения рабочей точки программы по командам текста программы и, самое главное, Вы должны понять зачем нужны подпрограммы задержек, их функции и предназначение, так как программирование без задержек, это все-равно что машина без мотора.
Все циклические подпрограммы построены на задержках и такой термин как "закольцовка" тоже "из этой же оперы".
Именно внутри задержек (колец больших, малых или еще каких-то) "скачет" рабочая точка программы, и именно они составляют "скелет" программы, да и сама программа вцелом представляет собой одно большое кольцо, внутри которого может быть какое-то количество колец рангом пониже, а в этих кольцах, в свою очередь, может находиться какое-то количество колец рангом еще ниже и т.д.
Движение рабочей точки программы по полному циклу программы можно сравнить со взлетом и посадкой самолета, между которыми он совершает несколько "мертвых петель".
В идеале, программист должен четко и ясно представлять себе функцию каждого кольца, его конструкцию и правила его функционирования.
Есть еще и другие, несомненно, важные вещи, на которые, изначально, "клюют" начинающие программисты, до конца не разобравшись с кольцевой "природой" программ, совершая тем самым тактическую (а может быть даже и стратегическую) ошибку.
Это может привести к трудностям в представлении общей картины работы программы, даже если человек неплохо разбирается с ее составными частями.
Так что не жалейте времени на "разборки" с задержками (кольцами), этот труд однозначно окупится.
И еще один добрый совет: если Вы хотите разобраться с работой программы, то не пожалейте времени на ее общий анализ и составление, по его результатам, блок - схемы программы. В дальнейшем, это сэкономит много времени и нервов.
А теперь, когда "скелет" выстроен, начинаем разбираться с другими важными вещами.

Организация вычисляемого перехода
 Понятие вычисляемого перехода было вкратце сформулировано во 2-м разделе (при описании регистра PCL).
Теперь - подробнее.
Нам уже известно, что команды ветвления "порождают" 2 сценария работы программы.
А как быть, если нужно "разветвиться", например, на 3 или 4, или 5 и т.д. сценариев?
А если при этом нужно осуществить безусловные или условные переходы (или их комбинации)?
Это можно сделать при помощи вычисляемого перехода (этот термин уже не моя фантазия, а стандартное название операции).
Вычисляемый переход осуществляется при помощи команды addwf PC,F, которая формально описывается так: сложить содержимое регистров W и PC, с сохранением результата сложения в регистре PC (имеется ввиду младший разряд счетчика команд с названием PCL).
Неформально, эта команда описывается так: увеличить (прирастить) текущее значение содержимого счетчика команд PC на величину содержимого регистра W.
То же самое можно сказать и о формальном толковании команды addwf PC,W. Разница только в том, что результат сложения будет сохранен в регистре W.
Таким образом, из-за того, что результат операции сохранится в регистре W, то будет осуществлено приращение содержимого регистра W, а не счетчика команд PC, и, по этой причине, команду addwf PC,W нельзя использовать в качестве команды вычисляемого перехода.
Итак, в случае организации вычисляемого перехода, применяется команда addwf PC,F. Эта команда может стоять в тексте программы где угодно (там, куда, в соответствии со своим замыслом, ее вставит программист) и, соответственно, адрес этой команды в счетчике команд PC может быть различным.
Так вот, для вычисляемого перехода, этот адрес является как бы начальной (нулевой) точкой отсчета.
Пример: программист составляет программу, и он "дошел до того ее места", где нужно "разветвить" программу, например, на 4 сценария (для того, чтобы впоследствии "уйти" на исполнение того или иного из этих 4-х сценариев).
Можно организовать две последовательные проверки, а можно организовать вычисляемый переход.
В последнем случае, можно обойтись количеством команд меньшим, чем в первом случае.
Такого рода выигрыш тем заметнее, чем на большее количество сценариев нужно "разветвиться".
Прежде чем вставить в текст программы команду addwf PC,F, необходимо определиться с критерием перехода, то есть, с содержимым чего-то, что определяет, какой именно из 4-х сценариев будет далее исполняться.
Это "чего-то" есть регистр W, а критерием перехода является его содержимое, то есть, число, находящееся в регистре W непосредственно на момент исполнения команды addwf PC,F.
То число, которое, на момент начала исполнения команды addwf PC,F будет находиться в регистре W и будет являться (после исполнения этой команды) приращением счетчика команд PC.
Повторяю, что команда addwf PC,F может иметь любой адрес в счетчике команд PC (куда ее "вставит" программист в текст программы, такой адрес в PC она и будет иметь) и, при вычисляемом переходе, этот адрес определяет только положение этой команды в счетчике команд PC и не влияет на выбор дальнейших сценариев работы программы.
Выбор того или иного сценария зависит от приращения к этому адресу.
Какие числа, в зависимости от выбираемого дальнейшего сценария работы программы, должны быть записаны в регистр W?
С точки зрения рациональности, значения этих чисел, в данном случае, должны быть равны либо 0, либо 1, либо 2, либо 3 (почему именно такие значения, а не 1, 2, 3, 4, Вы поймете позже), и количество этих чисел должно быть равно количеству сценариев.
При этом, после приращения содержимого счетчика команд PC, будет осуществлен безусловный переход (стек не задействован) на одну из четырех команд начала исполнения этих сценариев, которая будет исполнена в последующих машинных циклах.
В большинстве случаев, этих машинных циклов 2, если в качестве команд начала исполнения сценариев используются команды безусловных переходов, условных переходов, возвратов, но может быть и 1, если происходит переход на сценарий "программа исполняется далее".
Конечно же, можно "разбросать" команды начала исполнения этих 4-х сценариев по тексту программы, "тасуя их как колоду карт", можно даже "забросить" их "куда-нибудь подальше", предварительно закладывая в регистр W, например, двухзначные числа, но зачем нужны лишние проблемы, возникающие при этом?
Выгоднее всего команды начала исполнения всех сценариев размещать компактной группой сразу же после команды addwf PC,F.
Перед исполнением команды addwf PC,F, нужно откуда-то скопировать в регистр W какое-либо из чисел от нуля до трех включительно, и тогда, после исполнения команды addwf PC,F, произойдет переход ("скачек") рабочей точки программы на одну из следующих, после команды addwf PC,F, 4-х команд начала исполнения сценариев.
А уже после исполнения той или иной из этих 4-х команд, рабочая точка программы должна "пройти" определенную группу команд соответствующего сценария работы программы.
Откуда в регистр W могут быть записаны числа, значения которых определяют дальнейший выбор того или иного сценария работы программы?
В простейшем случае, эти числа формируются в результате опроса клавиатуры, а в нашем случае - опроса клавиатуры из двух кнопок, при помощи которых можно реализовать 4 комбинации нулей и единиц, то есть, проще говоря, 4 числа.
Если опросить клавиатуру из 3-х кнопок, то можно сформировать уже 8 чисел и, соответственно, реализовать 8 сценариев и т.д.
Есть и более сложные случаи, когда клавиатура не задействуется, а выбор того или иного сценария работы программы зависит от результатов подсчета импульсов, поступающих от внешних устройств, в том числе и с кодовым преобразованием (этот случай мы рассмотрим ниже).
"Скачек" рабочей точки программы на одну из следующих, после команды addwf PC,F, команд начала исполнения сценариев, происходит по принципу: если в регистре W, на момент исполнения команды addwf PC,F, записано число N, то при исполнении команды addwf PC,F, произойдет безусловный переход рабочей точки программы на команду начала исполнения сценария с номером N+1.
Обратите внимание, что в случае вычисляемого перехода, упомянутый выше безусловный переход происходит без применения команды безусловного перехода goto, а за счет приращения значения счетчика команд PC при исполнении команды addwf PC,F, а команды начала исполнения сценариев могут быть командами безусловного (goto) или условного (call) переходов или командой retlw (загрузка в регистр W 8-разрядной константы с возвратом по стеку), или текущей бит/байт - ориентированной командой (для сценария "программа исполняется далее").
Изложенное выше, при первом прочтении, может показаться чем-то сложным, но, фактически, это не так и при "разборе полетов" Вы в этом убедитесь.
Итак, ближе к практике.

Файл с примерами вычисляемых переходов addwfpc.asm Вы можете скачать: здесь.

Обращаю Ваше внимание, что этот файл не является программой (он является частью программы и не оформлен как программа) и проекта под него создавать не нужно.
Открыть его в MPLAB можно как обычный файл (File - Open).
Рассматриваем пример № 1.
Этот пример иллюстрирует принцип "разветвления" программы на 4 сценария, исполнение которых ставится в зависимость от состояния клавиатуры, состоящей из двух кнопок, подключенных между выводами RA0, RA1 порта А и корпусом.
Для того, чтобы обеспечить на выводах RA0 и RA1 единичные уровни при отжатых кнопках (кнопки - с нормально разомкнутыми контактами), необходимо подключить 2 резистора между выводами RA0 и RA1 (каждым из них) и цепью +5v.
Как это делается на практике, Вы можете посмотреть в принципиальной схеме частотомера - цифровой шкалы: здесь.
Кнопки подключаются между выводами J3 и J4 (каждым из них) и корпусом.
Резисторы R7 и R8 можно из схемы исключить, поставив вместо них перемычки.
В соответствии с изложенным выше принципом, команды "ухода" в сценарии должны располагаться компактно сразу же после команды addwf PC,F, что вы и видите в примере № 1.
В данном случае, вычисляемый переход на исполнение одного из 4-х сценариев выполняется следующим образом:
При помощи команды movf PortA,W производится опрос клавиатуры.
Так как команда movf является байт - ориентированной, то в регистр W записывается байт (8 битов).
Так как задействован порт А, имеющий 5 выводов, то в битах № 5, 6, 7 регистра PortA ПИКом всегда будут выставляться нули.
Клавиатура подключена со стороны младших разрядов и, при наличии нулей на выводах RA2, RA3, RA4 порта А, возможные числовые значения 8-битного числа будут строго определенными: 0, 1, 2, 3, то есть, всего 4 возможных значения (по количеству сценариев).
Почему не 1, 2, 3, 4 ?
А потому что, для того, чтобы, после исполнения команды addwf PC,F перейти, например, на следующую после нее команду (сценарий № 1), приращение счетчика команд PC должно быть нулевым.
Это объясняется тем, что счетчик команд PC автоматически инкрементируется при исполнении любой команды и поэтому, если формулировать принцип в общем виде, то его можно сформулировать так: для перехода на сценарий N, необходимо заложить в регистр W число N - 1.
Вывод из этого следующий: к моменту исполнения команды movf PortA,W, первые 2 вывода порта А должны быть настроены на работу "на вход", на выводах RA2 ... RA4 должны быть нулевые уровни.
В соответствии с этим ( с учетом того, что в битах регистра PortA № 5, 6, 7 выставлены нули - см. выше), возможные результаты опроса клавиатуры будут следующими:
0000 0000 - для 1-го сценария (.00/00h),
0000 0001 - для 2-го сценария (.01/01h),
0000 0010 - для 3-го сценария (.02/02h),
0000 0011 - для 4-го сценария (.03/03h).
При опросе клавиатуры, в регистр W записывается одно из этих чисел, и далее происходит приращение содержимого счетчика команд PC на величину этого числа, с учетом того, что, кроме всего прочего, содержимое счетчика команд PC автоматически инкрементируется при исполнении любой команды.
Например, для перехода на 3-й сценарий, содержимое счетчика команд PC увеличивается на 2 за счет суммирования содержимого счетчика команд PC и содержимого регистра W, и еще на 1 за счет автоматического инкремента содержимого счетчика команд PC при исполнении команды addwf PC,F.
Пример № 1 (см. файл addwfpc.asm) взят из текста программы частотомера - цифровой шкалы (см. главную страницу сайта).
В командах начала исполнения первых трех сценариев используются команды безусловных переходов (goto), а 4-м сценарием является сценарий "программа исполняется далее".
В зависимости от того, какая из первых трех команд goto исполняется, происходит безусловный переход на исполнение соответствующих подпрограмм (Function - 1-й сценарий, Plus - 2-й сценарий, Minus - 3-й сценарий).
Естественно, количество сценариев может быть и больше, и в последнем, четвертом, сценарии также можно использовать команду goto (все сценарии с безусловными переходами).
При "уходе" в какой-либо из сценариев, можно также использовать и команду условного перехода (call) с последующим возвратом по стеку на команду начала исполнения следующего сценария.
Возможности маневра здесь достаточно обширны и это зависит от конкретной конструкции программы.
Внимание: существует ограничение по количеству сценариев, а именно, количество сценариев не должно превышать 19-ти (включительно), иначе "приращение закольцуется".
Например, при переходе на 20-й сценарий, будет исполнен 1-й сценарий, при переходе на 21-й сценарий, будет исполнен 2-й сценарий и т.д. (в математике это называется суммированием по модулю с основанием 19).
Эти данные были мной получены экспериментально при работе над демонстрационной версией программы для частотомера - цифровой шкалы. В других ПИКах это ограничение может быть другим.
В примере № 2 показана организация вычисляемого перехода с целью преобразования двоично - десятичного кода в код 7-сегментного индикатора.
При конструировании устройств с отображением данных в одном или нескольких 7-сегментных индикаторах, такого рода преобразование (двоично-десятичного числа в код) является абсолютно необходимым.
Подпрограмму этого преобразования, построенную по классическому принципу, Вы сейчас и видите (ПП TABLE, но можно назвать ее как угодно).
После стандартной команды вычисляемого перехода addwf PC,F, компактно расположены 15 команд начала исполнения сценариев.
Пример взят из рабочей версии моей программы для частотомера - цифровой шкалы.
Расшифровка команды retlw k : в регистр W загружается стандартная 8-разрядная константа, после чего осуществляется возврат по стеку.
Откуда, что и куда? Непонятно.
Давайте разбираться.
Если речь идет о возврате, то сначала, естественно, нужно осуществить условный переход в ПП TABLE (gall TABLE). Команда retlw это та же команда return (возврат из подпрограммы по стеку), но только более "навороченная".
"Навороченность" ее заключается в том, что она совмещает в себе рабочую операцию (копирование в регистр W константы) и возврат по стеку.
При исполнении команды return, рабочей операции не совершается, а осуществляется только возврат по стеку.
Таким образом, команду retlw можно рассматривать как "мини"-подпрограмму, состоящую из одной рабочей команды (копирование в регистр W константы), после исполнения которой осуществляется автоматический возврат по стеку.
По этой причине, группу команд retlw можно рассматривать как команды совмещения начала исполнения различных сценариев, их исполнения и возврата по стеку после их исполнения (все в комплексе).
Вот такая "хитрая" получается команда и, кстати, очень удобная для программиста, так как она очень упрощает реализацию вычисляемого перехода в указанном выше случае.
После исполнения той или иной команды retlw (того или иного сценария), в регистре W "оседает" некое число (то, которое требуется получить после реализации данного сценария, а другими словами - один из возможных кодов).
В нашем конкретном случае, осуществляется кодовое преобразование чисел.
Посмотрите на первые 10 команд retlw и обратите внимание на значения констант, которые, для удобства восприятия, представлены в бинарном виде.
Сверьтесь с "раскладкой" секторов 7-сегментного индикатора (A,B,C,D,E,F,H), и Вы без особого труда заметите, что, например, после исполнения команды 1-го сценария, в регистр W скопируется число, отображающее в 7-сегментном индикаторе ноль, 2-го сценария - единицу, 3-го сценария - двойку и т.д.
Это означает, что, например, десятичное число 0, непригодное для отображения в 7-сегментном индикаторе, будет преобразовано в то же число 0, которое 7-сегментный индикатор отобразить сможет.
То же самое относится и ко всем остальным числам до 9 включительно.
Первые 10 команд retlw расположены в порядке возрастания значения чисел: первая команда сверху производит кодовое преобразование с числом 0, а последняя - с числом 9.
Принцип работы последующих 5-ти команд - такой же, только кодовое преобразование двоично - десятичных чисел (от 10 до 14) происходит не в цифры, а в латинские буквы с задействованием запятой, и константы представлены не в бинарной, а в десятичной системе исчисления.
Итак, после исполнения того или иного сценария, в регистре W "осело" число, соответствующее одному из нескольких символов, которые способен отобразить 7-сегментный индикатор.
В большинстве случаев, остается только скопировать это число из регистра W в регистр порта (его выводы должны быть настроены на работу на выход), к выводам которого подключен 7-сегментный индикатор.
Теперь опять вернемся к началу.
Каким образом должны формироваться значения чисел, копируемых в регистр W перед началом подпрограммы вычисляемого перехода TABLE?
Очевидно, что если команда retlw является командой возврата по стеку, то в тексте программы обязательно должна быть команда условного (по стеку) перехода на ПП TABLE - call TABLE.
Команда call TABLE может находиться в тексте программы или до, или после группы команд ПП TABLE, так как условный (и безусловный) переход можно осуществить из любого "места" текста программы.
В нашем случае, команда call TABLE находится до группы команд ПП TABLE.
Руководствуясь изложенным выше принципом, для случая применения 15-ти сценариев, с учетом того, что команды начала исполнения сценариев расположены компактно, максимальное значение числа, предварительно закладываемого в регистр W, должно быть 15-1=14, то есть, в регистр W, предварительно, должны закладываться числа от 0 и до 14.
Для первых 10-ти сценариев, значения этих чисел должны дублировать соответствующие цифровые значения символов, высвечиваемых в 7-сегментном индикаторе (0-0, 1-1, 2-2, ... 9-9).
К значениям чисел, предварительно закладываемых в регистр W для исполнения сценариев с 11 по 15 (числа с .10 по .14) должны быть "жестко привязаны" результаты выполнения операций установки признаков режимов, которые, в свою очередь, "жестко привязаны" к результату опроса состояния клавиатуры (режимы работы устройства переключаются при помощи клавиатуры).
Все эти "привязки" осуществляются до исполнения команды call TABLE и механизм их осуществления может быть различным.
В большинстве случаев, ПП вычисляемого перехода, подобная ПП TABLE, встраивается в циклическую подпрограмму опроса содержимого неких регистров общего назначения, в которые, после окончания подсчета импульсов (например, при измерении частоты), поразрядно записываются результаты этих подсчетов в двоично - десятичной форме исчисления.
В этом случае, на текущем "витке" циклической ПП опроса, перед исполнением команды addwf PC,F, в регистр W копируется содержимое одного из указанных выше регистров.
На следующем "витке" копируется содержимое следующего, по разрядности, регистра и так далее, до тех пор, пока в регистр W не будет скопировано (а, значит, в дальнейшем и отображено в 7-сегментных индикаторах) содержимое всех разрядов, после чего это копирование начинается снова и т.д., до тех пор, пока продолжается цикл индикации.
Например, если результат измерения (подсчета) представляет собой 8-разрядное двоично - десятичное число, то в качестве элементов оперативной буферной памяти назначаются 8 регистров общего назначения.
В ходе измерения, в эти регистры, с соблюдением разрядности (по порядку, обычно, начиная с младшего разряда), записываются результаты измерения (подсчета) в двоично - десятичном виде.
После этого, на каждом "витке" циклической ПП опроса, эти числа поразрядно копируются из соответствующего регистра общего назначения в регистр W, после чего начинает работать ПП вычисляемого перехода TABLE, по окончании работы которой, в тот же регистр W будет записано число (код), содержащее в себе информацию о символе, который способен отобразиться в 7-сегментном индикаторе.
После этого, содержимое регистра W копируется в регистр того порта, к выводам которого подключены выводы 7-сегментного индикатора (индикаторов).
А теперь предоставился удобный случай для того, чтобы, в общем виде, разобраться с таким важным понятием как динамическая индикация.
После прохождения 8-ми "витков" циклической ПП опроса (после опроса старшего разряда), происходит переход (по кольцу) на опрос младшего разряда и все повторяется снова и снова.
Вот Вам и типичный пример "двойной закольцовки": один "виток" большого кольца происходит за 8 "витков" малого кольца, расположенного внутри большого.
Количество же "витков" большого кольца "ставится на счетчик" и они будут "наматываться" до тех пор, пока этот счетчик не очистится.
После этого полный цикл динамической индикации заканчивается, после чего формируется следующее двоично-десятичное число и и полный цикл динамической индикации начинается снова и т.д..
Числовое значение каждого разряда индицируется в течение интервала времени формирования одного "витка" малого кольца индикации, в конце которого этот 7-сегментный индикатор переводится в пассивное состояние, а следующий по разрядности 7-сегментный индикатор активируется.
Таким образом, речь идет о последовательном, поразрядном выводе результатов измерения (счета) на индикацию.
Если ПП (или группа команд) вывода чисел на индикацию расчитана на применение одного 7-сегментного индикатора, то в нем будет осуществляться последовательная, поразрядная смена чисел, но гораздо больший практический интерес представляет случай, когда ПП вывода чисел на индикацию расчитана на применение многоразрядного (в нашем случае - 8-разрядного) 7-сегментного индикатора.
В этом случае, вывод чисел на индикацию производится также последовательно, но разряды чисел, выводимых на индикацию, "жестко привязываются" к разрядам 7-сегментного индикатора.
Например, крайний правый разряд 7-сегментного индикатора "привязывается" к младшему разряду числа, выводимого на индикацию и т.д. по порядку возрастания старшинства.
Специфика "привязки" разрядов чисел выводимых на индикацию к разрядам 7-сегментного индикатора заключается в том, что она осуществляется последовательной подачей напряжения на общие выводы (общий катод или общий анод) 7-сегментных индикаторов, а одноименные выводы секторов всех задействованных 7-сегментных индикаторов (7 секторов + запятая=8) "парраллелятся" и подключаются к восьми выводам порта, которые настроены на работу "на выход".
Для того чтобы последовательно активировать разряды линейки 7-сегментных индикаторов, необходимо сформировать адресный код управления этой активацией.
Для этого также нужно задействовать выводы порта.
Выводы порта В исключаются, так как они уже задействованы. Остается 5 выводов порта А, но их мало, так как выводов порта А пять, а разрядов линейки - 8.
Напоминаю, речь идет о PIC16F84A.
Если бы применялся другой, более "навороченный" микроконтроллер со "свободным" (незадействованным) портом на 8 выводов, то и не было бы затруднений: можно было бы подключить общие выводы 7-сегментных индикаторов к выводам этого порта, настроенным на работу "на выход", соответствующим образом составить программу и "дело с концом".
А как быть в нашем случае? Единственный разумный выход - применение внешнего (по отношению к ПИКу) дешифратора на 8 выходов, к которым и подключить общие выводы 7-сегментных индикаторов.
Как известно, для управления 8-ю выходами необходимо всего-лишь 3 адресных входа, а это уже вполне приемлемое количество, и для управления состояниями этих 8-ми выходов дешифратора можно задействовать 3 вывода порта А из пяти, подключив их к трем адресным входам дешифратора.
Составив соответствующим образом программу, можно сформировать на этих 3-х выводах порта А сигналы управления дешифратором, который, в свою очередь, будет осуществлять коммутации общих выводов восьми 7-сегментных индикаторов, что и требуется для динамической индикации.
Как это реализуется в принципиальной схеме, Вы можете увидеть, щелкнув: здесь. или: здесь.
Вы опять видите принципиальную схему частотомера - цифровой шкалы, программное обеспечение под которую я предлагаю на главной странице своего сайта.
На ней Вы видите ровно то, о чем говорилось выше: выводы порта В подключены к выводам секторов 7-сегментных индикаторов (одноименные секторы "запараллелены" внутри АЛС318), 8 общих выводов 7-сегментных индикаторов подключены к выходам дешифратора (555ИД7), а адресные входы дешифратора подключены к первым трем выводам порта А (на схеме они показаны как А0, A1, A2, но нужно читать RA0, RA1, RA2. То же самое относится и к выводам порта В).
Резисторы R2,4,5,6,9,10,11,12 - гасящие.
И еще один важный момент.
В примере №1 говорилось о вычисляемом переходе по внешнему управляющему сигналу, сформированному клавиатурой и имелось ввиду то же самое устройство, о котором и сейчас идет речь.
Посмотрите на схему: кнопки клавиатуры подключены к выводам RA0 и RA1 и, при опросе клавиатуры, эти выводы должны быть настроены на работу "на вход".
Но к этим же выводам подключены и первые два адресных входа дешифратора 555ИД7, а, в этом случае, выводы RA0 и RA1 должны быть настроены на работу "на выход".
Вопрос: "Как же тогда все это "мирно сосуществует" и работает в комплексе"?
Ответ: большую (ударение на букве о) часть полного цикла программы выводы RA0 и RA1 работают на выход и обеспечивают процесс динамической индикации.
Для опроса клавиатуры требуется очень малое время (доли процента от времени полного цикла программы).
В данном случае, опрос клавиатуры производится в начале полного цикла программы, и, непосредственно перед этим опросом, выводы RA0 и RA1 "перенастраиваются" с работы "на выход" на работу на "вход", а после окончания опроса сразу же "перенастраиваются" с работы "на вход" на работу "на выход".
Это "перенастройки" происходят так быстро, что, визуально, заметить их невозможно.
Вот Вам и типичный пример того, как одни и те же выводы портов задействуются для выполнения функционально различных операций, а "рулит всем этим делом" программа.
По этому же принципу можно задействовать группу выводов портов и для выполнения большего количества различных функциональных операций (возможность этого нужно рассматривать в каждом конкретном случае), что, согласитесь со мной, весьма существенно и особенно для микроконтроллеров, имеющих небольшое количество выводов портов (например, для PIC16F84A).
В данном же случае, без такого рода комплексного "задействования" просто не обойтись, так как, если выделять для каждой функциональной операции отдельные группы выводов порта А, то пяти его выводов окажется недостаточным и придется задействовать более "навороченный" микроконтроллер.
Работа с EEPROM памятью данных
 EEPROM память данных, так же, как и память программ, является энергонезависимой памятью, но к памяти программ она отношения не имеет и, по этой причине, она как бы "стоит особняком" и выполняет свои специфические функции.
Все эти функции, в конечном итоге, сводятся к сохранению либо констант, либо результатов операций (а если проще - чисел) после выключения питания микроконтроллера с целью их дальнейшего использования при следующем включении питания.
Например, в частотомере - цифровой шкале, описание которой находится на главной странице моего сайта, EEPROM - память задействована в двух случаях: в нее записывается, определяемое пользователем (и по умолчанию), значение промежуточной частоты, которое используется в дальнейшем при формировании показаний индикатора в режиме цифровой шкалы, и она же выполняет функции энергонезависимой памяти настроек (установка, после включения питания, тех режимов работы прибора, которые были установлены на момент предшествующего выключения питания).
В демонстрационной версии программы, которая предлагалась до замены ее рабочей версией, EEPROM память, дополнительно, задействовалась и в системе блокировки, которая срабатывала на третьем, после прошивки, включении питания, и в системе защиты от несанкционированных попыток изменения содержимого счетчика разрешенного количества включений питания.
Есть еще масса всевозможных вариантов задействования EEPROM - памяти данных, в результате которых, любое устройство на микроконтроллере можно сделать более "интеллектуальным" и удобным в использовании или/и ввести некие трудно устранимые пользователем ограничения в процесс его работы, проявляющиеся не сразу, а после превышения некоего "уровня", заданного программистом (защита от несанкционированного тиражирования, ограничение срока эксплуатации "по счетчику", дистанционное управление типа "блокировка/разблокировка" по радио, если программа составляется для устройств, имеющих в своем составе приемник, необратимая блокировка устройства при нарушении пользователем правил его эксплуатации и т.д.).
Что касается ограничений, то, честно говоря, это одно из моих "хобби", и не по той причине, что я "враг народа", просто это та область программирования, которая достаточно закрыта, а "запретный плод сладок" и очень интересен.
Хочу успокоить на этот счет тех людей, которые пользуются моей прошивкой Kea3.hex: никаких "сюрпризов", подобных описанным выше, программа не содержит и на этот счет можно быть абсолютно спокойным.
EEPROM - память, сама по себе, "тупая как валенок", так как из нее можно только либо считать данные, либо записать их в нее, и если речь идет о какой-то "интеллектуальности" при ее задействовании, то эту "интеллектуальность" нужно "закладывать" в рабочую часть программы.
В простейшем случае, данные (числа) в EEPROM - память записываются при "прошивке" ПИКа, а в ходе выполнения программы они периодически считываются из нее. При выключении питания эти константы не уничтожаются и с ними можно работать при последующих включениях питания.
В частотомере - цифровой шкале этот вариант задействования EEPROM - памяти применен для установки первичного значения промежуточной частоты (по умолчанию).
Другой случай: нужные программисту данные в EEPROM - память могут быть записаны при помощи специальных подпрограмм, организующих такую запись, и входящих в рабочую часть программы.
Для записи или чтения данных можно задействовать клавиатуру.
Таким образом можно многократно, по желанию пользователя, записывать в EEPROM - память различные группы констант, которые, после периодического считывания из EEPROM - памяти, используются в работе программы и которые сохраняются при выключении питания.
В частотомере - цифровой шкале этот вариант задействования EEPROM - памяти применен для организации режима работы "запись в EEPROM - память значения промежуточной частоты, определяемой пользователем".
Еще один случай: исходные данные в EEPROM - память записываются при "прошивке" ПИКа и, по ходу исполнения программы, периодически считываются в рабочую часть программы, затем видоизменяются в ней и в этом видоизмененном виде снова записываются в EEPROM - память.
Таких циклов "считывания/записи" может быть много (это определяет программист) и происходить они будут до тех пор, пока, считываемое в очередной раз из EEPROM - памяти число, не станет равным нулю или не "войдет в зону" чисел, которую задал программист, после чего в работе программы произойдут изменения (обратимые или необратимые - по желанию программиста), которые повлияют на дальнейшую работу устройства вне зависимости от количества коммутаций питания.
Упомянутые выше исходные данные можно записывать в EEPROM - память и с привлечением специальных подпрограмм, причем, в этом случае, можно организовать парольный доступ к устройству с блокировкой его работы в случаях введения неправильных паролей или каких-либо действий, запрещенных (с точки зрения программиста) для пользователя.
Можно еще перечислить множество различных случаев задействования EEPROM - памяти данных, и во всех случаях "рулить" ей будет либо одна только рабочая часть программы, либо она же, плюс директивы записи в EEPROM - память данных, располагающиеся в "шапке" программы.
Что это такое, Вы узнаете ниже.
Естественно, "обрушивать на Вас лавину информации", связанной с рассмотрением сложных случаев задействования EEPROM - памяти данных, я не буду. Примеры с этими случаями я привел только с той целью, чтобы показать Вам перспективность работы с EEPROM - памятью в смысле открывающихся при этом "богатых" возможностей.
На первых порах нужно понять, "что это вообще такое и с какого бока к этому подойти".
Напоминаю, что EEPROM - память это энергонезависимая память, данные в которой сохраняются при выключении питания.
Объем ее не велик: для большинства ПИКов (в том числе и для PIC16F84A) она составляет 64 байта, то есть, 64 ячейки, в каждую из которых может быть записан один байт (число от .00 до .255), и из каждой из которых этот байт (число) может быть считан и использован при исполнении программы.
Каждая ячейка EEPROM - памяти имеет свой адрес в диапазоне адресов от .00 до .63 (00h - 3Fh), который обязательно нужно указывать как при чтении данных из EEPROM - памяти, так и при записи данных в EEPROM - память.
Содержимое какой именно ячейки, и в какой именно момент времени исполнения программы, нужно считать - определяет программист.
В какую именно ячейку, и в какой именно момент времени исполнения программы, нужно произвести запись - также определяет программист.
Запись в ту или иную ячейку EEPROM - памяти того или иного числа производится "поверх старого" числа, то есть, отдельного процесса стирания "в чистом виде" нет, а есть процесс замены одного числа другим.
До начала исполнения рабочей части программы, в EEPROM - память может быть произведена предварительная запись данных.
Этим "рулит" директива (или несколько директив) DE, которая должна располагаться в "шапке" программы до директивы установки программы на начало (org), и речь о которой пойдет ниже.
Для того чтобы устроить "разбор полетов", поставим перед собой простую, вполне конкретную и, на практике, очень распространенную задачу:
До начала исполнения рабочей части программы, необходимо записать в EEPROM - память число, например, .100 (64h) в ячейку EEPROM - памяти с адресом 02h.
Затем, в ходе исполнения программы, нужно скопировать содержимое этой ячейки (.100) в регистр буферной памяти (регистр общего назначения) с названием, например, Registr, после чего, произвести какую-нибудь операцию с содержимым этого регистра (с числом .100), например, увеличить его содержимое на единицу (результат - .101) с сохранением результата операции в этом же регистре Registr, и затем скопировать число .101 в ту же ячейку EEPROM - памяти с адресом 02h.
По ходу этих "разборок" я буду объяснять некоторые общие положения работы с EEPROM - памятью.
Обращаю Ваше внимание на следующую, хотя и достаточно примитивную, но, "имеющую право на жизнь", аналогию, которая поможет Вам понять суть работы с EEPROM - памятью.
Ячейки EEPROM - памяти можно представить себе в виде специфических регистров общего назначения.
И в самом деле, через регистры буферной памяти (те же регистры общего назначения), с содержимым этих ячеек можно производить те же самые операции, что и с содержимым регистров общего назначения, и задачу, сформулированную выше (а также множество других задач), если не требуется сохранить данные после выключения питания, можно решить, задействуя только регистры общего назначения, что, в этом случае, всегда и делается на практике.
К "услугам" ячеек EEPROM - памяти, обычно, прибегают только тогда, когда "деваться некуда" (если не сохранить данные после выключения питания, то не будет реализована задумка программиста).
Главное отличие ячеек EEPROM - памяти от регистров общего назначения в том, что они сохраняют данные после выключения питания, а это отражается на их "управляемости" в том смысле, что операции, производимые с содержимым ячеек EEPROM - памяти, довольно-таки громоздки и требуют большего количества команд, чем операции, производимые с регистрами общего назначения.

Файл с примером работы с EEPROM-памятью eeprom.asm
Вы можете скачать: здесь.

Этот файл не является полноценной программой. Проекта под него создавать не нужно, а просто откройте его (File - Open).
Вы видите конструкцию "программы" (в кавычках потому, что она "неполноценна") с задействованием EEPROM памяти данных, в которой решается задача, сформулированная выше.
Все, что относится к работе с EEPROM - памятью, расписано подробно. Остальная часть "программы" обозначена условно - строками из точек. Это относится и к "шапке" "программы", и к ее рабочей части.
На месте этих строк, проще говоря, может быть "все что угодно".
Это "все что угодно", в данный момент, не существенно, а нам нужно разобраться с тем, как предварительно (до исполнения рабочей части "программы") записать в ячейку EEPROM с адресом 02h число .100, затем, в рабочей части "программы", считать это число в регистр буферной памяти, увеличить его на 1 и, после этого, записать число .101 в ту же ячейку EEPROM.
Сначала разберемся с предварительной записью.
Предварительная запись данных в ячейки EEPROM - памяти производится при помощи директивы MPLAB с названием DE.
Прежде чем применить эту директиву, необходимо обратиться к EEPROM - памяти (активизировать, включить, запустить - кому как нравится) при помощи директивы org с указанием адреса 2100h (см. текст примера).
Директива org 2100h всегда трактуется однозначно (других вариантов "включения" предварительной записи в EEPROM - память нет) и поэтому можно "не забивать себе голову" информацией о том, что такое 2100h, а просто принять это как данность (запомнить).
После этой директивы должны располагаться одна или несколько директив DE.
При помощи директивы DE в ячейки EEPROM - памяти записываются данные (числа от .00 до .255).
Вопрос: по каким адресам и в каком порядке?
Ответ: первая, после директивы org 2100h, директива DE, записывает первое, указанное в рабочей части этой директивы, число (крайнее левое) в ячейку EEPROM - памяти с адресом .00 (00h), второе число записывается в ячейку с адресом .01 (01h), третье - в ячейку с адресом .02 (02h) и т.д., вплоть до последней 64-й ячейки (если в этом есть необходимость).
Значения чисел (в любой системе исчисления), записываемых в ячейки EEPROM - памяти, должны быть указаны в рабочей части директивы DE и расположены в порядке следования адресов, по которым эти числа нужно записать (начиная с нулевого и далее по порядку), с отделением этих чисел друг от друга запятыми.
В нашем случае, первая директива DE записывает числа 0h, 0h, 64h (.100) в 1, 2, и 3 ячейки соответственно (эти числа можно изобразить и в десятичной системе исчисления, и в бинарной, но, в последнем случае, это не совсем удобно).
Примечание: обратите внимание на форму представления числа "ноль" (0h).
Берите на заметку: 00h, 000h, 0000h и т. д., это одно и то же, что и 0h. Все незначащие нули слева от крайней левой значащей цифры можно не указывать (а можно и указывать - кому как удобнее), но крайняя правая цифра всегда является значащей (в том числе и 0) и, в случае применения нулевого числа, крайний правый ноль должен указываться всегда.
Это относится и к 16-ричной форме представления чисел, и к десятичной (бинарная - не в счет).
Например, .0034 = .034 = .34 = 0022h = 022h = 22h, а .0020 = .020 = .20 = 0014h = 014h = 14h.
По ходу работы я могу манипулировать различными формами представления (в основном - десятичной и 16-ричной) и орфографией чисел для того чтобы, в конечном итоге, вся эта "канцелярия" не вызывала у Вас затруднений.
В нашем случае, число .100 (64h) нужно записать в 3-ю ячейку с адресом .02h
Так как первая, после директивы org 2100h, директива DE "жестко привязана" к начальной, нулевой точке отсчета (адрес .00), то, для того чтобы "дойти" до третьей ячейки, нужно чем-то заполнить первые две ячейки (записать в них какие-то числа).
Если бы эти 2 ячейки в программе задействовались, то в них нужно было бы записать определяемые программистом числа, но, в нашем случае, они не задействованы и то, какими именно числами они будут заполнены, не имеет значения: в них можно записать любое число, но, обычно, в такого рода "пустышки" записываются нули, что Вы и видите в тексте "программы".
Это связано с тем, что, по умолчанию, во все ячейки EEPROM - памяти записываются числа FFh (кроме ячеек, заполняемых при помощи директивы DE), и на этом "фоне" тот "сектор" EEPROM - памяти, в котором записаны нули "сразу бросается в глаза", что делает более комфортным визуальное восприятие содержимого области EEPROM - памяти (элемент удобства).
Я специально, нерационально задействовал ту ячейку EEPROM - памяти, до которой нужно "добираться", создавая никому не нужные "пустышки", чтобы показать "как они плодятся и размножаются".
Это я "ушел" еще недалеко (всего-лишь адрес 02h), а если задействовать, например, 64-ю ячейку с адресом 3Fh?
В этом случае, рабочая часть директивы DE будет представлять собой длиннющую строку, заполнение которой, по сути своей, является просто бестолковой работой.
Практический вывод из этого следующий: если необходимо задействовать ячейки EEPROM - памяти, то, с точки зрения рациональности, нужно их задействовать, начиная с первой ячейки (адрес .00) и далее по-порядку (если нужны несколько ячеек).
В этом случае, до задействованной ячейки не нужно "добираться, плодя пустышки", и рабочая часть первой директивы DE будет самой компактной и удобной для восприятия.
Если руководствоваться этими соображениями, то, в нашем случае, нужно задействовать не 3-ю, а 1-ю ячейку EEPROM - памяти (в рабочей части программы обращаться не к ячейке с адресом 02h, а к ячейке с адресом 00h).
В этом случае, рабочая часть директивы DE будет содержать в себе не 3 числа, а одно число 64h (.100) и дело с концом. Далее можно переходить к "разборкам" с рабочей частью "программы", а директива DE будет первой и последней.
Конечно, предлагаемый Вашему вниманию пример "программы" можно составить и таким образом (на практике так и нужно делать), но, в обучающих целях, пусть она будет такой, какая есть, иначе отсутствовал бы наглядный предмет разбирательств.
Вопрос: неужели "пустышки" так уж и бесполезны?
Ответ: нет.
Представьте себе ситуацию: на момент начала составления текста программы, программист точно знает, что "железобетонно" потребуется одна ячейка EEPROM, в которую нужно записать константу до начала исполнения рабочей части программы, но по ходу составления текста рабочей части программы, может возникнуть необходимость задействования еще нескольких групп ячеек, например, двух групп, состоящих из двух ячеек.
В этом случае, директиву DE можно "оформить" так: DE 0h,0h,64h,0h,0h,0h или так: DE 0h,0h,0h,0h,64h,0h или так: DE 0h,0h,64h,01h,01h,02h или как-то еще (по своему желанию) с тем, чтобы, как бы, "зарезервировать" ячейки под будущие операции с EEPROM - памятью, а заодно и как-то визуально выделить из общей "массы" группу ячеек, с которыми, в дальнейшем, будет работать программа (элемент удобства).
Например, последний пример директивы DE (DE 0h,0h,64h,01h,01h,02h) можно истолковывать так: ячейки 1 и 2 и ячейки 4 и 5 зарезервированы под "еще не рожденные" операции с использованием EEPROM - памяти, в 3-ю ячейку закладывается константа .100 под операции с EEPROM - памятью, которые однозначно будут производиться, а 6-я ячейка - "ефрейторский зазор" (на всякий случай).
Короче, подобного рода "прибамбасы" - дело вкуса.
По большому счету, ни на какие принципиально важные вещи они не влияют и можно вообще обойтись без них (просто "дойти" до задействованной ячейки и на этом остановиться), а говорю я о них только по той причине, что подобного рода "архитектурные излишества" часто сбивают с толку начинающих программистов, "внося смятение в умы" и получается, что, для создания целостного и ясного представления о предмете разбирательства, необходимо учитывать и подобного рода детали.
К примеру, рассмотрим директиву DE 0h,0h,64h,0h,0h,0h.
Если поместить ее в "шапке" программы на свое "законное место", то своей правой частью она "наедет" на вертикальную "виртуальную" линию, которая, условно, проходит через точки с запятыми комментариев.
По этой причине, начало комментария этой директивы придется сдвинуть вправо по отношению к началу других комментариев, что создает "легкий бардачек" в тексте программы.
Конечно же, это абсолютно не "смертельно", так как комментарии, в отличие от содержимого 1-го и 2-го столбцов, могут занимать любое место справа от последних символов строк 2-го столбца, лишь бы не было "наезда" точки с запятой комментария на "хвост" содержимого 2-го столбца.
Конечно же, можно вставить комментарии в текст программы и по этому принципу ("кто в лес, кто по дрова"), в работе программы от этого ничего не изменится, но это не есть "правило хорошего тона", и если этот "бардак" носит массовый характер, то он доставляет неудобства не только тем людям, которые с ним разбираются, но и самому автору программы (особенно когда он, со временем, кое-что подзабудет).
Так вот, чтобы навести в этом вопросе порядок, директиву DE 0h,0h,64h,0h,0h,0h целесообразно разделить на 2 части так, как это сделано в примере "программы".
Если рабочая часть директивы DE содержит большее количество чисел, то, по такому же принципу, одну эту директиву можно "расчленить" на 3, 4, 5 и т.д. частей, использовав 3, 4, 5 и т.д. директив DE.
В этом случае, начальной точкой отсчета адреса для "нижней" директивы, будет последний адрес директивы, располагающейся "одним этажом выше".
Директива DE "может" работать не только с числами, указанными в явном виде (см. выше), но и с символами.
Это означает, что если соответствующим образом оформить рабочую часть директивы DE, то в ячейки EEPROM - памяти будут записаны числовые значения символов (букв, цифр, знаков препинания, специальных символов).
При этом, в рабочую часть директивы DE можно вводить не числа, а символы, заключив группу этих символов в кавычки.
Посмотрите в текст "программы".
Вы увидите, что, при помощи третьей сверху директивы DE, я вставил в незадействованные ячейки (начиная с 7-й) комментарий.
Такого рода "архитектурные излишества" реализуются по принципу: "Если есть свободное место, то дай-ка я в него что-нибудь засуну, хуже от этого не будет".
Естественно, без такого рода комментариев вполне можно обойтись, это зависит от желания программиста.
Если нужно разбить текст комментария на части, то это делается по принципу, описаннному выше, только каждая такая часть заключается в кавычки.
В тексте "программы" я этого делать не стал.
Таким образом, в большинстве рационально составленных программ, задействующих EEPROM - память, задействованные в программе ячейки обычно располагаются "плотной" группой в начале EEPROM - памяти (начиная с нулевого адреса), а в незадействованных ячейках, по умолчанию, записывается FFh.
Другое дело, если нужно спрятать (замаскировать) в EEPROM - памяти некую секретную ячейку/ячейки (например, задействованные в счетчике блокировки).
В этом случае как раз и потребуется то, о чем говорилось выше.
Такого рода секретные ячейки можно "разбросать" (если их несколько) в области EEPROM - памяти как угодно, "заполнив промежутки" между ними какой-нибудь "абракадаброй".
Для такого "заполнения" применяются те же директивы DE с рабочими частями, в которых указывается либо случайно выбранные значения чисел, либо случайно выбранные символы, либо и то, и другое.
В идеале, лучше всего, сначала отладить программу, перевести получившиеся секретные константы в символы и затем "встроить" эти символы в некий осмысленный или бессмысленный набор символов, который нужно предварительно записать в ячейки EEPROM - памяти по образу и подобию рассмотренной выше записи комментария (в тексте "программы" - самая нижняя директива DE). Усвоив то, о чем шла речь выше, Вы без особых проблем разберетесь с тем, как это делается.
Проконтролировать содержимое области EEPROM - памяти в MPLAB можно так: в главном меню щелкнуть по слову Window и в раскрывшемся списке щелкнуть по строке EEPROM Memory.
Откроется окно EEPROM Window, в котором Вы и увидите ячейки EEPROM - памяти с их содержимым.
Здесь же можно определить и адрес любой ячейки.
Если Вы откроете окно EEPROM Window при открытом файле примера "программы", то увидите в нем значения чисел, установленных по умолчанию FFh (во всех ячейках), хотя, казалось бы, в основную их часть должна быть произведена запись чисел, отличных от FFh (в соответствии с текстом "программы" примера).
В чем дело? А дело в том, что, если ассемблирования текста программы ни разу не производилось, то в окне EEPROM Window Вы увидите во всех ячейках значения чисел, установленных по умолчанию (FFh).
Если текст программы проассемблирован, то, если EEPROM - память в программе не задействовалась - никаких изменений не произойдет (FFh - во всех ячейках так и останется).
Если проассемблирован текст программы, задействующей EEPROM - память, то, при помощи директивы DE, в соответствующие ячейки EEPROM - памяти запишутся предварительно устанавливаемые константы и/или числа (в 16-ричной форме исчисления) , соответствующие символам (если они используются), значения которых (после ассемблирования) можно проконтролировать в окне EEPROM Window.
Обращаю Ваше внимание на следующий общий принцип: в окнах, показывающих содержимое области оперативной памяти, памяти программ и EEPROM памяти данных, отражается результат последнего ассемблирования.
Это означает, что если в текст программы внесены изменения и, после этого, ассемблирования произведено не было, то Вы не сможете проконтролировать эти изменения.
Вопрос: а если изменения вносятся не в текст программы, а в комментарии или в оформление программы (во все то, что располагается правее точек с запятыми). Нужно ли, в этом случае, производить ассемблирование?
Ответ: есть 2 варианта сохранения изменений в комментариях и/или в оформлении программы.
Первый вариант: произвести ассемблирование.
В этом случае, в HEX-файле ничего не изменится (MPLAB "не видит" ничего, что расположено правее точек с запятыми, а левее их никаких изменений не произошло), но, кроме HEX-файла, при ассемблировании обновляется и ASM-файл (текст программы), а в нем сохраняются все изменения, происходящие с текстом программы.
Второй вариант: так как HEX-файл обновлять не нужно, то можно сохранить указанные выше изменения только в ASM-файле.
Это делается при помощи стандартной операции: в главном меню MPLAB нужно щелкнуть по слову File, а в выпадающем списке - по строке Save.
На первых порах, проще всего (чтобы не "распылять внимание" на исполнение операции, без которой вполне можно обойтись), при любых изменениях в тексте программы, производить ассемблирование.
Вернемся к вопросу контроля предварительной записи данных в EEPROM - память в случае использования "программы", описываемой в примере.
С учетом изложенного выше, эту "программу" нужно проассемблировать.
Если Вы попробуете ее (файл eeprom.asm) проассемблировать, то получите сообщение о 21 ошибке и в окне EEPROM Window ничего не изменится (во всех ячейках FFh), что является закономерным результатом неисполнения требований к оформлению текста программы.
"Заругался" MPLAB абсолютно обоснованно, так как "программа" примера неправильно оформлена и является "неполноценной".
Вопрос: а как же, в этом случае, при помощи симулятора, убедиться в том, что директивы DE исполняются?
Ответ: никак.
Вывод: проверить результаты исполнения директив DE можно в том случае, если текст оформлен в виде "полноценной" программы, и этот текст проассемблирован.
Это относится к директивам, исполнение которых приводит к записи в память (оперативную или энергонезависимую, то есть, ту, в которую можно "заглянуть", используя симулятор) тех или иных чисел.
Например, директива DE входит в эту группу, а директива equ - не входит, и визуально (в симуляторе) проконтролировать ее исполнение программист не может.
За "корректностью оформления" директивы equ (и всех остальных директив) следит MPLAB и, если "что-то не так", то MPLAB, после ассемблирования, выдаст соответствующее сообщение об ошибке и укажет, в какой именно строке текста программы содержится ошибка.
В заключение "разборок" с предварительной записью данных в EEPROM - память, обращаю Ваше внимание на пояснения, находящиеся в тексте примера между "шапкой" "программы" и ее рабочей частью.
Вы видите варианты заполнения ячеек EEPROM - памяти для 2-х случаев "оформления" директив DE.
Это именно то, что будет наблюдаться в окне EEPROM Window после проведения ассемблирования, если предположить, что текст примера оформлен как "полноценная" программа.
Разбираем текст "программы".
 До и после интересующих нас операций, "программа" может работать множеством различных способов и реализовывать какие угодно замыслы программиста.
Для нас, в данном случае, это не важно, а важно то, как происходит работа с EEPROM - памятью.
На момент начала исполнения рабочей части "программы", 46 ячеек EEPROM - памяти заполнены числами ("сработала" директива DE), а остальные ячейки заполнены числами, устанавливаемыми по умолчанию (FFh).
Из всех этих ячеек, нас интересует только одна - третья, с адресом 02h, так как в ней, предварительно, записано число, с которым нужно производить интересующие нас операции (64h или .100).
В этом смысле, все остальные предварительно записанные числа можно рассматривать как "архитектурные излишества", не влияющие на ход исполнения "программы".
В данном случае, ухода в прерывания нет.
Исполнение "программы" начинается стандартно, с ПП START. В рабочей части "программы" я не стал обозначать начало этой ПП, имейте это ввиду.
После "запуска" рабочей части "программы", она исполняется до тех пор, пока не возникнет необходимость в работе с EEPROM - памятью, после чего, рабочая точка программы должна "перейти" на первую команду группы команд, обеспечивающей работу с EEPROM - памятью.
В данном случае, переход на эту первую команду происходит "естественным образом" (с предыдущей, по отношению к команде bcf Status,RP0, команды).
Переход на первую команду, указанной выше группы команд, может также происходить и из любого "места" "программы", если для этого используются команды переходов goto или call.
В этом случае, группе команд, обеспечивающей работу с EEPROM - памятью, нужно "присвоить статус" подпрограммы или выставить метку на первой команде этой группы для того, чтобы в рабочей части команды goto или call указать, куда нужно осуществить переход.
Разбираемся с тем, что же должна из себя представлять эта группа команд?
В соответствии с сформулированной выше задачей, это должна быть группа команд, обеспечивающая считывание числа из 3-й ячейки EEPROM - памяти в буферный регистр оперативной памяти.
Следовательно, нужно назначить, в качестве буферного регистра оперативной памяти, один из регистров общего назначения.
Назначаем ему название, например, Registr и "прописываем" его в "шапке" программы по адресу, например, 0Ch.
Теперь мы конкретно знаем, с чем нам работать (откуда считывать и куда "закладывать на временное хранение" результат считывания) и остается только выяснить, как конкретно это сделать?
Вы видите перед собой "кучу" не вполне понятных команд и, по всей видимости, думаете, что "разборки" с ними будут достаточно трудоемкими.
Ничего подобного.
Группа команд чтения данных из EEPROM - памяти (и записи - тоже) является стандартным набором команд, рекомендованных к применению разработчиками и "изобретать колесо" не нужно.
Единственное, что требуется от программиста - вставить эту стандартную группу команд в нужное место текста программы, далее, в нужном месте (внутри этой группы) выставить адрес ячейки EEPROM - памяти, из которой производится чтение (в нашем случае - 02h) и название регистра буферной оперативной памяти, в который запишется считываемое число (в нашем случае - Registr).
О том, что происходит в "дебрях" ПИКа, программисту вовсе знать не обязательно, и, если указанные выше данные для осуществления чтения, не содержат ошибок, то "реальный" ПИК или "виртуальный" ПИК симулятора, все сделает "в лучшем виде".
То же самое относится и к процессу записи данных в EEPROM - память.
Тем не менее, хотя бы для "общего развития", разобраться с этим стандартным набором команд стоит.
По рекомендации разработчиков ПИКов, чтение данных из EEPROM - памяти начинается с перехода в нулевой банк (bcf Status,RP0).
Если в ходе составления программы выяснится, что, на момент начала процедуры чтения данных из EEPROM - памяти, происходит работа в нулевом банке, то команду bcf Status,RP0 из текста программы можно (и нужно) удалить (а какой смысл "уходить" в нулевой банк, если работа и так происходит в нем).
А можно и "не забивать себе голову" этой проверкой и оставить все как есть (это - для "ленивых").
Следующие 2 команды - стандартные команды закладки константы, о которых многократно говорилось ранее.
В данном случае, константой является адрес 3-й ячейки EEPROM - памяти (02h).
Обратите внимание, что число 2 указано без атрибутов систем исчисления (10-чной или 16-ричной).
В этом случае, MPLAB (ПИК) "воспринимает" его как 02h, то есть, как 16-ричное число (о том, в каких случаях можно указывать число без атрибутов, я объяснял ранее).
Эта константа закладывается в регистр специального назначения EEAdr, который используется только при работе с EEPROM - памятью. Число, которое в него закладывается, определяет адрес той ячейки, из которой будет производиться считывание.
Вот Вам и место закладки адреса ячейки EEPROM - памяти, из которой будет производится считывание (берите на заметку).
Итак, ячейка выбрана и далее нужно инициализировать чтение (разрешить считывание из нее числа).
Этим делом "рулит" нулевой бит регистра EECon1, который (регистр) находится в первом банке.
Следовательно, нужно перейти в первый банк, установить в 1 нулевой бит регистра EECon1 и опять вернуться в нулевой банк, так как следующая команда (movf EEData,W) обращается к регистру, расположенному в нулевом банке.
После команды перехода в нулевой банк, располагается группа из двух команд.
После исполнения первой команды этой группы (movf EEData,W), предварительно записанное в ячейку с адресом 02h, число 64h (.100) копируется в регистр W (аккумулятор), а после исполнения второй команды этой группы (movwf Registr), число 64h (.100) копируется из регистра W в регистр буферной оперативной памяти Registr, с содержимым которого (с числом 64h), в дальнейшем, можно произвести ту или иную операцию.
Вот Вам и ответ на вопрос: в какое место группы команд чтения данных из EEPROM - памяти нужно "вставить" название буферного регистра?
Регистр специального назначения EEData (регистр данных) задействуется только при работе с EEPROM - памятью (также, как и регистр EEAdr - регистр адреса).
Теперь, в соответствии с алгоритмом работы "программы", нужно произвести операцию инкремента.
Итак, после исполнения команды movwf Registr, процедура чтения данных из 3-й ячейки EEPROM - памяти закончилась и можно увеличить на 1 число .100, хранящееся в регистре Registr.
Это делается при помощи команды incf Registr,F.
После ее исполнения, в регистре Registr будет находиться число .101 (65h), которое, в соответствии с сформулированным выше алгоритмом работы "программы", необходимо записать все в ту же 3-ю ячейку EEPROM - памяти.
Делаем это.
Разработчики ПИКов рекомендуют начинать процедуру записи с команды глобального запрета прерываний bcf Intcon,GIE.
Обратите внимание, что номер бита (7-го) регистра Intcon в команде не указан по той причине, что в "шапке" программы биту с номером 7 директивой equ присвоено название GIE и, по этой причине, можно указывать не номер бита, а его название.
Можно эту команду оформить так: bcf Intcon,7, и в этом случае из "шапки" программы можно убрать строку GIE equ 7.
Глобальный запрет прерываний также можно осуществить при помощи байт - ориентированной команды clrf Intcon.
Если, на момент исполнения одной из этих команд, в 7-м бите регистра Intcon записан 0, то (по аналогии с командой bcf Status,RP0) эту команду из текста "программы" можно исключить.
Необходимость запрета прерываний обусловлена тем (за исключением случая "планового ухода" в прерывания по окончанию записи в EEPROM), что, если на момент начала записи в EEPROM - память разрешены прерывания по окончанию записи в EEPROM (см. 6-й бит регистра Intcon), то, после окончания этой записи, рабочая точка программы "уйдет" в ПП прерываний.
Даже если программа и не содержит ПП прерываний, такого рода "уход" может привести к ошибкам записи.
Руководствуясь этим, можно не сбрасывать в ноль 7-й бит, а сбросить в ноль 6-й бит, но, обычно, применяют глобальный запрет прерываний "и дело с концом".
После команды глобального запрета прерываний следуют 2 команды выбора ячейки EEPROM - памяти.
Они в точности такие же, как и команды выбора ячейки EEPROM - памяти при чтении (см. выше) и останавливаться на них я не буду.
После выполнения следующей пары команд, число .101 (65h) через регистр W переписывается из регистра буферной памяти Registr в регистр специального назначения EEData и на этом процесс предварительной подготовки к записи заканчивается.
Теперь нужно сначала разрешить, а затем и произвести запись.
Запись в EEPROM - память разрешается установкой в 1 второго бита регистра EECon1, а он находится в первом банке, следовательно, перед тем, как это сделать, нужно перейти в 1-й банк (bsf Status,RP0).
После разрешения записи (bsf EECon1,2) можно произвести эту запись.
Для этого используется обязательная последовательность из пяти команд (см. текст примера).
При этом производятся операции с физически нереализованным регистром EECon2, смысл которых достаточно "туманен" (разработчики говорят об этом очень "скупо").
Программисту от этого "ни тепло и ни холодно", так как, в этом случае, возможность "маневра" начисто отсутствует и, по этой причине, ничего, кроме этой обязательной последовательности команд, он и не сможет применить.
Так что, просто применяем ее, не "перегружая свои мозги" информацией, без знания которой вполне можно обойтись.
После исполнения обязательной последовательности команд, флаг прерывания по окончанию записи в EEPROM - память устанавливается в 1 (флаг поднят).
Сбрасывается этот флаг только программно (принудительно), что Вы и видите (bcf EECon1,4), после чего можно "уйти" из первого банка в нулевой (bcf Status,RP0).
Вопрос: почему переход в нулевой банк осуществляется именно в этом месте, а не ранее?
Ответ: потому, что предшествуюшие команды, начиная с команды bsf EECon1,2, работают с регистрами 1-го банка, а если конкретно, то регистр EECon1 расположен в 1-м банке, а регистру EECon2 вообще "безразлично", в каком именно банке с ним работают, так как он не реализован физически.
В тексте примера предполагается, что следующие после команды bcf Status,RP0 операции будут производиться с регистрами нулевого банка (вероятность этого - большая), но если, после завершения процедуры записи, следующие операции будут производиться с регистрами первого банка (вероятность этого - маленькая), то команду bcf Status,RP0 нужно как бы "вырезать" из конца процедуры записи и перенести ниже по тексту программы, "вклинив" ее в то место, где кончаются операции с регистрами 1-го банка и начинаются операции с регистрами нулевого банка.
Осталось только обратить внимание на "шапку" программы.
Рассмотрев ее, Вы сможете убедиться, что все, относящееся к рассмотренной части "программы", в ней "прописано", и вопрос "что откуда взялось" не должен вызывать у Вас затруднений.
В заключение, советую Вам записать рассмотренные выше стандартные команды процедур чтения и записи на листок и "приобщить" его к имеющимся у Вас распечаткам или сохранить их в виде отдельного файла.
Примечание: выше рассмотрены процедуры "чтение - преобразование - запись" в пределах только одного полного цикла "программы".
Не трудно догадаться, что произойдет при исполнении последующих полных циклов "программы": с каждым циклом, значение числа, записываемого в 3-ю ячейку EEPROM - памяти, будет увеличиваться на 1 до числа .255 включительно, после чего, на следующем полном цикле "программы", произойдет переход от числа .255 к числу .00, затем к .01, затем к .02 и т.д. до тех пор, пока не будет выключено питание.
Число, записанное в 3-й ячейке EEPROM - памяти, после выключения питания будет сохранено, и, при следующем включении питания, вся эта "свистопляска" начнется именно с этого числа.
Конечно, какого-то практического смысла во всем этом не видно, но для обучающей "программы" это простительно.
Если необходимо придать такого рода "программе" хоть какой-то практический смысл, то можно "нафантазировать" следующее: полный цикл программы является очень большим, например, 24 часа и необходимо, чтобы через 256 - 100 = 156 суток работы устройства (без учета времени, в течение которого оно выключено), либо в его работе что-нибудь изменилось, либо оно что-то включило/выключило.
В этом случае, критерием такого рода "изменений" или "включений/выключений" должно быть равенство числа, записываемого в 3-ю ячейку EEPROM - памяти, нулю, но критерий может быть и другим, например, .180. Тогда время "выдержки" уменьшится до 180 - 100 = 80-ти суток.
Естественно, та часть "программы", которую я условно обозначил точками, должна быть составлена под решение конкретной задачи.
И еще:
В ПИКах предусмотрены меры по предотвращению случайной записи в EEPROM память данных при сбоях в работе программы, снижении напряжения питания и других ненормальных режимах работы ПИКа.
При установленном бите защиты (см. биты конфигурации), считать данные из EEPROM памяти при помощи программатора невозможно. На работе программы внутри ПИКа это не отражается.
При записи данных в EEPROM - память, теоретически, могут быть совершены ошибки, но, практически, вероятность их очень мала.
В "особо ответственных" программах, задействующих EEPROM - память, организуются проверки правильности записи, которые представляют собой процедуру сверки предварительно создаваемого "эталона" c фактическим (считываемым после записи) результатом записи.
По результатам этой сверки делается вывод, должна ли программа исполняться далее или нужно повторить запись до получения положительного результата, после чего исполнить программу далее.
Переходите к следующему разделу.


Kea_33

13. Флаги. Работа с флагами. Как работает цифровой компаратор. Перенос и заем.

Ранее, во 2-м разделе, я упоминал о флагах. Пришла пора с некоторыми из них разобраться.
Посмотрите в распечатки регистров специального назначения. Названия битов флагов выделены зеленым цветом.
Естественно, что слово "флаг" нельзя воспринимать буквально. Здесь имеет место быть аналогия, которая в упрощенном виде выглядит так: поднятие флага означает сигнализирование о том, что произошло какое-то конкретное событие, а опущенный (сброшенный) флаг означает то, что этого конкретного события не произошло.
Так как этих "конкретных событий" может быть несколько, то и каждый из флагов "привязывается" к вполне определенному событию.
Например, один флаг "реагирует" на факт наличия или отсутствия нулевого результата произведенной операции (флаг Z), другой флаг "реагирует" на факт наличия или отсутствия переполнения сторожевого таймера (флаг -TO), третий - на факт наличия или отсутствия переноса - заема (флаг C) и т.д.
Исходя из этого, становится понятным и их практическое предназначение: анализируя состояния флагов, программист может осуществлять контроль за результатом исполнения задуманных им операций, осуществляемых при помощи команд, к которым "привязаны" те или иные флаги.
Их ("привязки") Вы можете увидеть в распечатке команд.
Это очень удобно, так как программист не может "заглянуть" внутрь ПИКа при помощи измерительных приборов и флаги, в этом случае, как бы выполняют функции этих приборов.
Существуют 2 группы флагов.
К первой группе относятся флаги, поднятия или сбросы которых зависят от результатов выполненных команд.
Например, если командой DECF (но не командой DECFSZ, так как она не оказывает влияния на флаги - см. распечатку команд) осуществляется декремент числа .01, то после первого декремента (результат операции .00) флаг нулевого результата поднимется (бит Z установится в 1), а после следующего декремента (результат операции .255) - сбросится (бит Z установится в 0).
К второй группе относятся флаги, факт поднятия которых зависит от того, какая именно операция производится, но сбрасывать которые нужно программно.
Проще говоря, один раз поднявшись, эти флаги не сбросятся до тех пор, пока их "принудительно" не сбросишь программными средствами, обратившись непосредственно к этим флагам (битам).
Например, в ранее рассмотренной программе Retr_1, флаг внешнего прерывания по входу INT с названием INTF, при наличии внешнего прерывания по входу INT, устанавливается в 1 (поднимается), и если его, до момента выхода из ПП прерываний (команда retfie) не сбросить в 0, то, при дальнейшей работе программы, факт поднятия этого флага будет "восприниматься" ПИКом как наличие управляющего сигнала "ухода" в прерывание по входу INT со всеми вытекающими последствиями (работа программы нарушится).
Именно по этой причине флаг INTF, до команды retfie, нужно обязательно сбросить.
Посмотрите в распечатки регистров специального назначения.
Те флаги, в комментариях к которым указано "сбрасывается программно", относятся к 2-й группе флагов, а те флаги, в комментариях к которым этого нет - к 1-й группе.
Таким образом, флаги 1-й группы можно назвать "безобидными", так как, если они не задействованы в работе программы, то на работу программы они влияния не оказывают.
Проще говоря, в этом случае, они "всем скопом" могут как угодно подниматься и сбрасываться, - программа этого "не заметит".
Флаги 2-й группы уже не такие "безобидные", так как за их состояниями нужно следить (учитывать) и программно их сбрасывать после того, как они поднялись в результате исполнения соответствующей операции.
Если этого не делать, то "наказание" последует незамедлительно.
Посмотрите в распечатку команд.
В 5-м столбце с названием "Флаги" Вы увидите названия флагов (битов флагов), на которые воздействует та или иная команда.
Есть команды, которые воздействуют сразу на несколько флагов (например, ADDWF), есть команды, которые воздействуют на один флаг (например, CLRF), а есть и команды, которые не оказывают влияния ни на один флаг (например, DECFSZ).
Если Вы сравните общее количество различных флагов в 5-м столбце распечатки команд и в распечатках регистров специального назначения (обозначены зеленым цветом), то Вы заметите, что в распечатке команд их количество меньше, чем в распечатках регистров специального назначения.
Объяснение этому следующее: в распечатках регистров специального назначения Вы видите все флаги, а результаты исполнения конкретных команд воздействуют только на их часть.
В 5-м столбце распечатки команд Вы не найдете флагов второй группы.
Проанализируйте содержимое этого столбца и Вы убедитесь, что наиболее часто встречается флаг нулевого результата Z , и именно по этой причине он заслуживает к себе особого внимания.
Возникает вопрос: могут ли флаги, кроме контрольных функций, выполнять какие-либо более значительные функции, например, каким-то образом воздействовать на ход исполнения программы?
Ответ: да, могут.
Выше уже говорилось о том, к чему может привести отсутствие программного сброса флагов 2-й группы, а это есть не что иное, как прямое воздействие на ход исполнения программы.
Флаги 1-й группы также могут влиять на ход исполнения программы, но только несколько иным, косвенным способом.
Далее будем работать с самым "распространенным" флагом - флагом нулевого результата Z, с которым, на практике, происходит значительная часть операций с задействованием флагов.
Принцип работы с флагом Z следующий: перед выполнением команды (эта команда должна воздействовать на флаг Z), флаг Z сбрасывается (2-й бит регистра STATUS устанавливается в 0), а потом выполняется команда.
Результат ее выполнения (число) может быть нулевым или не нулевым, то есть возможны 2 сценария.
Из этого следует, что, если проанализировать состояние флага Z после исполнения команды, то можно "разветвить" программу на 2 сценария и, что самое главное, поставить в зависимость "уход" рабочей точки программы в тот или иной сценарий от ответа на вопрос: каков результат исполнения команды - нулевой или не нулевой? Такого рода анализ осуществляется при помощи бит- ориентированных команд ветвления btfsc или btfss, анализирующих состояние бита флага Z, после чего происходит ветвление на 2 сценария, в один из которых рабочая точка программы "уходит" при нулевом результате исполнения, предшествующей команде btfsc (btfss), команды, а в другой - при результате исполнения команды не равном нулю.
Пример: допустим, что необходимо составить программу под устройство типа "реле времени" (например, для фотопечати), то есть, имеется кнопка, после нажатия на которую производится отсчет заданного интервала времени.
Таким образом, речь идет о счетчике, который, конечно же, можно реализовать так, как это сделано в программах Multi или cus.
Но, в нашем случае, необходимо "родить" вычитающий счетчик, работающий по принципу анализа состояния флага нулевого результата Z (берите на заметку, это еще одна конструкция счетчика).
Выглядеть этот счетчик будет так:
...........................
bcf Status,2
decf ABC,F
btfsc Status,2
goto XYZ
...........................
ABC - название регистра общего назначения, XYZ - название подпрограммы или метки, а строки с точками - предыдущие и последующие команды программы.
Вы видите, что этот счетчик "встроен" в программу и, по логике работы устройства, он должен начать работать только после нажатия на кнопку запуска (клавиатура).
В этом случае, пока кнопка не нажата, рабочая точка программы должна "крутиться" где-то в программе, "обходя" указанную выше группу команд, и, при этом, состояние клавиатуры должно опрашиваться каждый полный цикл программы.
После нажатия на кнопку и последующего очередного опроса клавиатуры, рабочая точка программы "закольцовывается" в некой циклической подпрограмме.
Указанная выше группа команд должна входить в кольцо этой циклической подпрограммы, выйти из которой она может только при условии, что флаг нулевого результата поднялся (установился в 1).
Итак, при нажатии на кнопку, рабочая точка программы "рано или поздно" установится на команду bcf Status,2, после исполнения которой 2-й бит регистра STATUS сбрасывается в ноль (сброс флага Z).
Следующей исполняется команда декремента decf ABC,F.
В регистре общего назначения, условно названном ABC, на момент начала счета находится какое-то число (константа), например .10 (определяет время выдержки).
После исполнения операции декремента, число .10 уменьшается до .09 и сохраняется в том же регистре ABC.
Далее, командой btfsc Status,2 производится анализ состояния бита флага Z.
Если результат исполнения операции декремента не равен нулю, то флаг Z как был сброшен, так сброшенным и останется.
Следовательно, если результат не нулевой, то команда goto XYZ будет проигнорирована (вместо нее - "виртуальный" NOP) и рабочая точка программы "уйдет" в сценарий типа "программа исполняется далее", а так как упомянутая выше циклическая подпрограмма представляет собой замкнутое кольцо, то, через один "виток" полного цикла этой подпрограммы, рабочая точка программы снова окажется на команде bcf Status,2, затем произойдет следующий декремент, число уменьшится до 08. и т.д.
На 10-м "витке" флаг нулевого результата Z поднимется (установится в 1) и произойдет переход рабочей точки программы на исполнение другого сценария, то есть в некую условную подпрограмму с названием XYZ (или на метку), после чего рабочая точка будет "гонять" по другим кольцам полного цикла программы, обходя указанную выше группу команд, в ожидании следующего нажатия кнопки.
В этом случае, время задержки будет равно времени прохождения рабочей точкой программы 10-ти "витков" циклической ПП, формирующей время выдержки и, изменяя это число (константу), можно изменять продолжительность выдержки.
Задействуя флаг Z, можно реализовать такое нужное и распространенное устройство, как цифровой компаратор, то есть, устройство, осуществляющее анализ "входного" числа на предмет его "попадания" (или нет) в определенную "зону" чисел, с последующим "уходом" рабочей точки программы в один из двух сценариев ее исполнения.
Эта "зона" чисел, в зависимости от желания программиста, может включать в свой состав от одного до нескольких чисел.
Цифровой компаратор может быть частью, например, системы фазовой автоподстройки частоты, дешифратора, системы блокировки и т. д., то есть, весьма обширного класса устройств.
Файлы с примером реализации цифрового компаратора:

Файл с формализованными комментариями flag.asm: здесь.

То же самое с неформальными комментариями (flag_1.asm): здесь.

Проект под них в MPLAB создавать не нужно, просто откройте (File - Open).
Вы видите пример "программы", в которой задействуется цифровой компаратор.
Сначала, в общем виде, проанализируем принцип работы этой "программы".
Цифровой компаратор анализирует "поступающие" на его вход числа на предмет их "попадания или нет в зону" чисел от .00 до .04 включительно.
Примечание: в данном случае, числа располагаются "плотной группой", но можно и "разбросать" их по всему диапазону чисел (от .00 до .255) или сместить эту "плотную группу" по числовой оси в любое "место".
При соответствующем наращивании количества команд, можно расширить эту "зону", а при сокращении - уменьшить вплоть до одного числа.
В соответствии со сказанным выше, в результате "протаскивания" рабочей точки программы через цифровой компаратор, она должна "неизбежно уйти" в один из двух сценариев работы программы.
В данном случае, "уход" в эти сценарии происходит после выполнения команд безусловных переходов goto YES или goto NO, то есть, один сценарий будет исполняться после перехода рабочей точки программы в ПП YES, а другой - в ПП NO. В них может делаться "все, что угодно", это зависит от замысла "программы".
Названия этих подпрограмм - условно и "обозвать" их можно как угодно.
Также можно считать их не подпрограммами, а метками, если переходы осуществляются не на первые команды подпрограмм.
Я расположил ПП NO вверху, а ПП YES внизу (по тексту "программы"), но они могут располагаться где угодно, так как безусловный (и условный) переход может осуществляться в любое "место" рабочей части программы.
Для того, чтобы откуда-то "извлекать" числа для последующего их анализа, необходимо, в качестве регистра буферной памяти, назначить и "прописать" в "шапке" "программы" регистр общего назначения. Назовем его Registr (см. "шапку" "программы").
На момент исполнения первой команды группы команд, при помощи которых реализуется цифровой компаратор, в регистре Registr должно быть записано анализируемое число.
Откуда берутся эти числа, в данном случае не важно. Существует множество вариантов, при помощи которых они "рождаются", и если рассматривать их все, то можно очень далеко уйти от рассмотрения конкретного вопроса, так что оперируем с неким числом, "природа возникновения" которого не важна.
Приглядевшись к командам, реализующим цифровой компаратор, Вы должны заметить аналогию с рассмотренным выше примером.
Так как командой, результат исполнения которой анализируется, является команда операции с константой (sublw), предполагающая задействование регистра W, то, перед ее исполнением, необходимо скопировать в регистр W число, "находящееся" в регистре Registr, что и делается командой movf Registr,W.
Флаг нулевого результата Z проще и удобнее всего сбросить непосредственно перед командой, результат исполнения которой анализируется (sublw).
Во многих случаях можно сбросить этот флаг и раньше, но, с точки зрения "привязки" к простым и понятным "правилам игры", целесообразнее всего руководствоваться именно этим правилом, причем, во всех случаях задействования флага Z для анализа результатов исполнения тех или иных команд, влияющих на состояния этого флага.
Результатом исполнения команды sublw .00 будет ноль только в единственном случае, когда содержимое регистра Registr также будет нулевым (0-0=0).
Во всех остальных случаях, результатом исполнения команды sublw .00 будет число не равное нулю.
После исполнения команды sublw .00, результат ее исполнения анализируется командой btfsc Status,Z, во время исполнения которой происходит анализ состояния бита флага Z и, в зависимости от результата этого анализа, рабочая точка программы либо "уходит" в ПП YES (если в регистре Registr было записано число .00), либо она продолжает свое движение по сценарию "программа исполняется далее", то есть, переходит на следующую группу команд, такую же, как и предшествующая, но только с использованием числа .01.
В ходе исполнения этой группы команд, образно выражаясь, дается ответ на вопрос: "Равно ли анализируемое число числу .01 или нет?" Если да, то рабочая точка программы "уходит" все в ту же ПП YES, а если нет, то она "переходит" на следующую группу команд, работающим с числом .02 и т.д. до числа .04 включительно.
Если не в одной из пяти этих групп команд не произошел "уход" рабочей точки программы в ПП YES (анализируемое число равно либо .00, либо .01, либо .02, либо .03, либо .04), то после исполнения пятой группы команд (анализ числа .04), рабочая точка программы "уйдет" в ПП NO (анализируемое число не равно не одному из чисел .00, .01, .02, .03, .04).
В ПП YES и NO "может делаться все, что угодно", например, в простейшем случае, ПП YES может включить, а ПП NO - выключить (или наоборот) некое внешнее исполнительное устройство. Естественно, в них могут быть реализованы и более сложные функции.
На месте команды goto NO может также находиться и очередная команда "программы", не связанная с переходом, а команда, следующая за командой btfsc Status,Z, может быть либо командой перехода (условного или безусловного), либо командой, которая совершает операцию за один машинный цикл, например, bsf PortA,X (где X - любой из 5-ти битов регистра PortA, который нужно установить в 1).
В случае исполнения этой команды, на следующем "витке", число, "находящееся" в регистре Registr, снова копируется в регистр W, после чего оно снова анализируется на соответствие (или нет) другой константе и т.д.
В этом случае, установка того или иного бита регистра PortA в 1 (а можно и в 0, если использовать команду bcf) "привязывается" к конкретному значению числа, находящегося в регистре Registr на момент начала работы компаратора.
Любые 5 чисел из диапазона чисел от .00 до .255 можно "привязать" к конкретному выводу порта А и если, например, к этим выводам подключены исполнительные устройства, то можно "привязать" моменты включений или выключений этих устройств к параметрам внешних управляющих сигналов (к амплитуде, частоте, фазе, скважности).
Могут быть и другие варианты, здесь "есть где развернуться".
В качестве примера практического использования цифрового компаратора можно привести пример реализации (в упрощенном, общем виде), допустим, устройства пожарной сигнализации, реагирующего на температуру:
имеется датчик температуры, который, например, работает в диапазоне от 0 до 100 градусов Цельсия.
Необходимо, чтобы в диапазоне температур от 80 до 90 градусов сработало некое устройство предупреждения (например, сирена), а в диапазоне температур от 91 до 100 градусов включилась система автоматического пожаротушения.
Предположим, что датчик линейный, и изменению температуры от 0 до 100 градусов соответствует изменение напряжения на его выходе от 0 до 10v.
Предположим, аналоговый сигнал датчика переведен в цифровую форму и, в конечном итоге, программа сработала таким образом, что изменению напряжения от 0 до 10v линейно соответствует изменение значений чисел, записываемых в регистр Registr, в диапазоне от .00 до .100 (дискретность 0,1v или 1 градус).
В этом случае, в команды первой группы проверок должны быть заложены константы от .80 до .90, а в команды второй группы проверок - от .91 до .100.
Можно либо поменять местами эти группы, либо "перетасовать указанные выше цифры как колоду карт", на работе устройства это не отразится, но, с точки зрения порядка, лучше распределить эти цифры в порядке их возрастания.
А теперь все зависит от степени "навороченности" замысла программиста:
если она низкая, то можно подключить управляющий вход исполнительного устройства к какому-нибудь выводу порта, настроенному на работу "на выход" и использовать для переключения его состояний команды bcf или bsf.
Если она высокая, то лучше, используя команды переходов, "уйти" в соответствующую подпрограмму (подпрограммы), так как, в этом случае, требуется значительное количество команд, которые "не затолкаешь" в один машинный цикл.
Например, требуется, чтобы исполнительное устройство сработало по совокупности событий: высокая температура и задымленность.
Если первой анализируется температура, то, если в регистре Registr находится число, например, от .80 до .90, осуществляется переход на исполнение подпрограммы (подпрограмм) анализа задымленности, в состав которой должна входить, аналогичная рассмотренной, группа команд цифрового компаратора, работающая с числами, величины которых зависят от уровня выходного сигнала датчика задымленности, и только после одновременных утвердительных ответов на вопросы: "Уровень задымленности выше нормы?" и "Температура выше 90 градусов?", исполнительное устройство пожаротушения должно сработать.
Естественно, при этом конструкция и устройства и программы усложняется.
Примечание: в данном случае, и если речь идет о применении команд переходов, то, в пределах каждой из двух анализируемых "зон" чисел (в данном случае они одни и те же), эти команды должны обращаться к одной и той же подпрограмме (метке) примерно так, как это сделано в примере "программы". При попадании в "зону температуры", рабочая точка программы должна совершить переход в одну исполнительную подпрограмму, а при попадании в "зону задымленности" - в другую.
Существуют и комбинированные варианты "ухода" рабочей точки программы из группы команд, реализующих цифровой компаратор.
Например, в пределах одной функционально обособленной "зоны" чисел, при равенстве анализируемого числа одной константе, могут задействоваться все те же команды bcf или bsf(см. выше), а при равенстве анализируемого числа другой константе, могут задействоваться команды переходов.
В простейшем случае, например, в случае использования в цифровом компараторе команд bcf или bsf, он "напрямую" воздействует на исполнительное устройство, но, в большинстве случаев, и цифровой компаратор, и другие устройства, реализуемые при помощи ПИКа, являются всего-лишь элементами целой цепочки (комплекса) устройств, при помощи которых осуществляется управление исполнительными устройствами (устройством), состояния которых могут быть многовариантными.
В этом случае, звенья этой цепочки воздействуют на исполнительные устройства как бы опосредованно, "внося свой посильный вклад в общее дело". Примером исполнительного устройства, состояния которого могут быть многовариантными, может служить известные всем 7-сегментные индикаторы, а в качестве примера опосредованного воздействия все того же цифрового компаратора на их работу, я могу привести процесс гашения незначащих нулей в показаниях линейки из 8-ми 7-сегментных индикаторов (всех левых нулей, левее левой значащей цифры) в моей конструкции частотомера - цифровой шкалы.
При этом применено устройство типа описанного выше цифрового компаратора для "зоны" чисел, в состав которой входит всего одно число (.00).
8 таких последовательно соединенных компараторов анализируют 8 чисел, отображаемых в линейке, начиная с левого (старшего) разряда.
При наличии нулей в показаниях индикаторов линейки до первой значащей цифры, происходит гашение сегментов этих индикаторов, а остальные разряды остаются "нетронутыми", так как рабочая точка программы, в этом случае, при помощи команд безусловного перехода, "уходит" в сценарий типа "гашение игнорируется".
"Фундаментом всего этого сооружения" и других подобных устройств, является цифровой компаратор, а его "сердцем" - флаг нулевого результата Z, так что он вполне заслуживает к себе внимания.
Разбираемся с флагами C и DC.
 Если принцип работы флага Z не является чем-то уж очень сложным, то понимание принципов работы флагов переноса - заема C и десятичного переноса - заема DC для большинства начинающих является своеобразным "камнем преткновения".
Постараюсь убедить Вас, что "не так страшен черт, как его малюют", а заодно, на примере флагов C и DC, расскажу о методике проверок операций, которые вызывают у программиста затруднения.
Механизм этой методики следующий: создается некая группа команд, "обслуживающая" выполнение операции, вызывающей затруднения.
Для того чтобы можно было "полноценно" работать в симуляторе, необходимо "вставить" эту группу команд в любую программу, в которой регистр (регистры) специального назначения, использующийся в этой группе команд, был "прописан" в "шапке" программы (или назначить и "прописать" его).
В противном случае, после ассемблирования, будет выдаваться сообщение об ошибке типа "происходит обращение к регистру, которого нет".
Эту группу команд целесообразнее всего "поставить" где-то в начале программы, чтобы долго до нее не "добираться", при этом то, как работает программа после этой группы команд - совершенно безразлично. Все внимание - на "вставленную" группу команд.
Давайте сделаем такую манипуляцию для выяснения принципов работы флагов C и DC.
"Родим это дитя от суррогатной матери", в качестве которой используем, например, уже известную Вам, простенькую программу Multi.asm.
Откройте проект Multi.
Давайте прикинем, куда "вставить" указанную выше проверочную группу команд.
Можно "вставить" ее в самое начало программы, но при этом нужно будет переносить название ПП START на первую команду этой группы.
Можно сделать и так, но лучше "оставить в покое" название подпрограммы, а "врезаться" где-нибудь пониже.
В "промежутке" между командами перехода в первый банк и возвращения из него в нулевой банк, эту "врезку" делать нельзя, так как проверочная группа команд (по крайней мере первая команда этой группы), в подавляющем большинстве случаев, работает в нулевом банке.
Следовательно, "не мудрствуя лукаво", "врезаем" проверочную группу команд в "промежуток" между командами bcf Status,5 и movlw .32.
Теперь определяемся с ее составом.
Если необходимо разобраться с флагами C и DC, то, естественно, нужно применить команду, которая воздействует на оба этих флага.
Посмотрите в распечатку команд: таких команд 4, две - сложения и две - вычитания.
Операция сложения, на мой взгляд (а кто-то может думать по-другому), более проста и привычна, чем операция вычитания, значит "отсеиваем" 2 команды вычитания.
Смотрим на команды сложения ADDWF и ADDLW. Можно применить и ту, и другую, но, с точки зрения простоты конструкции проверочной группы команд, выгоднее использовать команду операции с константой ADDLW, а не байт - ориентированную команду операции с содержимым регистра ADDWF, так как, в последнем случае, требуется задействование регистра общего назначения и запись в него чисел в 2 приема (через регистр W).
То есть, при применении команды ADDWF потребуется 5 команд, плюс задействование регистра общего назначения, а при применении команды ADDLW потребуется всего 3 команды и никаких регистров задействовать не нужно.
Итак, определились, - используем команду ADDLW, которая означает: сложить содержимое регистра W с 8-разрядной константой.
Следовательно, до исполнения команды ADDLW, нужно записать в регистр W какое-то число (константу).
Таким образом, мы имеем 2 константы (первое и второе слагаемые), значения которых предстоит складывать для того чтобы выяснить: "Что будет происходить с флагами C и DC, если делать различными значения этих констант?" Для того чтобы в симуляторе выяснить, когда поднимаются флаги C и DC, перед выполнением команды ADDLW их необходимо сбрасывать (устанавливать в ноль).
Следовательно, перед исполнением команды ADDLW, должна исполниться команда bcf Status,0, если работаем с флагом C или bcf Status,1, если работаем с флагом DC.
Получается следующее:
для работы с флагом переноса - заема C
movlw X
bcf Status,0
addlw Y
для работы с флагом десятичного переноса - заема DC
movlw X
bcf Status,1
addlw Y
X и Y - числа, с которыми будут производиться операции.
Разбираемся с флагом C.
Вставьте указанную выше группу команд (для работы с флагом C) в текст программы Multi.asm между командами bcf Status,5 и movlw .32.
Естественно, X и Y пропечатывать не нужно, просто оставьте пустое место для чисел констант. Их можно будет "вписать" позднее.
Все готово для того чтобы "выпытать" у флага C все его "тайны".
Методика этой "пытки" очень проста: в качестве чисел X и Y подставляем различные их значения (от .00 до .255) и смотрим, что из этого получится.
Сразу возникает 2 вопроса: "Где смотреть и какие значения чисел подставлять?"
Ответ: смотреть - в окне RAM или SFR, а из чего исходить при выборе значений этих чисел (констант) - давайте разбираться.
Посмотрите в комментарий флага C.
В нем говорится о каком-то старшем бите и не оговаривается - старшем бите чего?
Полубайт (4 бита) отпадает, так как с ним работает флаг DC (см. распечатку).
Остается байт, следовательно идет речь о переносе из старшего бита байта.
Байт может отобразить значения чисел от .00 до .255, а старший бит байта устанавливается в 1 во второй половине этого диапазона чисел (начиная с .128) и сбрасывается из 1 в 0 при переходе от числа .255 к .00.
Вполне логично "привязаться" к этому наиболее "резкому цифровому скачку" (переход от .255 к .00) и предположить, что при этом должно что-то произойти, а именно: флаг C должен на этот "скачек" среагировать, то есть, подняться (установиться в 1).
Теперь анализируем: "В каком случае результат выполнения команды ADDLW может быть большим, чем .255 с учетом того, что операция сложения производится с двумя 8-битными числами?"
Ответ однозначен: если сумма этих чисел больше, чем .255.
Но в этом случае происходит "выход" на второй "виток" счета, так как 8-битное число не может быть большим, чем .255, и на втором "витке" счета сумма, отражаемая в 8-битном числе будет представлять собой число, значение которого не превысит .255.
При этом один "виток" счета как бы "исчезает".
Если, в этом случае, не принять мер к увеличению разрядности регистра, в котором отображается сумма, то, в каждом случае превышения значения суммы над числом .255, будет "исчезать" количество чисел, равное 256 (числа с .01 по .255 плюс число .00).
В ПИКе нельзя реализовать, например, 9, 10, 11, ... - разрядный регистр, а можно только реализовать разрядность кратную 8-ми, задействуя один или несколько 8-мибитных регистров и получая при этом увеличение разрядности, что, по сути своей, есть способность к точному отображению "больших" чисел.
Количество задействованных стандартных 8-битных регистров определяется в каждом конкретном случае и зависит от максимальной величины чисел, которые могут быть получены в результате операций с другими числами (например, при сложении, как в нашем случае).
Итак, возникла необходимость в наращивании разрядности.
Как это делается, например, при использовании двоичного счетчика 555ИЕ5?
Выход старшего разряда этого счетчика подключается к счетному входу еще одного счетчика и этот второй счетчик начинает считать число полных циклов счета первого (младшего) счетчика (количество чисел - 16).
Так называемым выходом переноса, в данном случае, является выход триггера старшего разряда, а сигналом переноса является перепад напряжения, формируемый при переходе от числа .15 к числу .00.
А ведь регистры ПИКа имеют такую же основу (триггеры), что и двоичные счетчики по образу и подобию 555ИЕ5, только с большей разрядностью, следовательно, нарастить разрядность регистров можно по такому же принципу.
Строго говоря, это аналогия близкая, но не полная. Почему? Об этом ниже.
Тогда возникает вопрос: "А как технически реализовать в ПИКе перенос из старшего разряда предшествующего регистра в младший разряд последующего регистра?"
Чувствуете, "куда ветер дует?" А "ветер дует" на флаг C.
В его названии есть слово "перенос" и "следит" он за байтом.
А что если флаг C реагирует на полный цикл счета в пределах байта и, в случае превышения фактического числового значения результата операции, числа .255 (выход на следующий цикл счета), он поднимается? Самостоятельно проверьте это предположение, используя указанные выше команды проверки для флага C.
Меняя числа X и Y, посмотрите на состояние флага C (нулевой бит регистра STATUS, адрес регистра 03h) в окне RAM (а можно и в окне SFR), в случаях, если их сумма меньше .255 и больше .255.
Работайте в пошаговом режиме.
Не забывайте, после изменения значений чисел X или Y, каждый раз производить ассемблирование текста программы, а также то, что, по определению, константа есть 8-битное число (значение этого числа не должно превышать .255).
Настоятельно Вам рекомендую на время отвлечься от дальнейшего чтения и поманипулировать с числами X и Y в проверочной группе команд, обращая внимание на зависимость состояния флага C от величины суммы этих чисел в случаях работы в пределах первого "витка" счета и в случаях работы в пределах второго "витка" счета.
Пол-часа такой работы более эффективны, чем несколько часов теории.
Обычно, после такого рода практической работы, наступает значительное "просветление в мозгах" и человек может даже удивиться, насколько простым оказалось то, что ранее вызывало затруднения.
Итак, что выясняется?
Выясняется следующее: если арифметический результат сложения двух 8-мибитных чисел больше, чем число 255, то флаг С установится в 1. В этом случае, речь идет об операции переноса.
Если этот арифметический результат меньше, чем число .255 и находится в "зоне" чисел от .00 до .255, то флаг C установится в 0.
До сих пор речь шла об операции сложения. Давайте разберемся с операцией вычитания.
Если Вы замените, в проверочной группе команд, команду сложения ADDLW на команду вычитания SUBLW(а также замените команду bcf Status,0 на bsf Status,0. Почему? Поймете позже) и произведете, аналогичную описанной выше (с учетом того, что производится операция вычитания), проверку, то выясните следующее:
если арифметический результат вычитания двух 8-мибитных чисел является числом положительным, и он находится в "зоне" чисел от .00 до .255, то флаг C установится в 1.
Если этот арифметический результат меньше, чем число 0, то есть, результат вычитания является отрицательным числом, то флаг C установится в 0. В последнем случае, речь идет об операции заема.
 Вот вам и ответ на вопрос: "Откуда в комментарии к флагу C взялось словосочетание: "перенос - заем?"
Обязательно следует уточнить, что перенос (при суммировании) или заем (при вычитании) возникает не во всех случаях суммирования или вычитания, а только в тех, когда результат суммирования или вычитания "выходит за рамки" интервала чисел от .00 до .255 (с того или иного "края"), то есть, в тех случаях, когда арифметический результат суммирования больше числа 255 и когда арифметический результат вычитания является отрицательным числом.
В этих случаях говорится так: "суммирование с переносом" и "вычитание с заемом" соответственно, а во всех остальных случаях речь идет просто о суммировании и просто о вычитании.
И еще один существенный момент:
Образно выражаясь, число суммы или разницы, при "выходе" его из "зоны" чисел .00 - .255, никуда не "исчезает", а отображается в этой же "зоне", только, как бы, с противоположного "конца".
Примеры:
Если суммируются числа, например, .254 и .03, то суммой будет являться не число .257 (в 8-мибитном регистре отобразить его невозможно), а число .02 плюс перенос (флаг C установился в 1).
Если, например, из числа .01 вычитается число .03, то результатом вычитания не будет являться отрицательное арифметическое число. Им будет являться число .253 плюс заем (флаг C установился в 0).
Если Вы сравните эти примеры, то получите ответ на последнюю "неясность" в комментарии к флагу C (см. распечатку), а именно: "Что означает словосочетание "заем имеет инверсное значение?"
И в самом деле, при переносе, бит флага С устанавливается в 1, а, при заеме, бит флага С устанавливается в 0.
Если значение бита флага С при переносе считать, как бы, "начальной точкой отсчета", то значение бита флага С при заеме, по отношению к этой "начальной точке отсчета", будет иметь инверсное значение.
А теперь давайте разберемся с "легким бардачком", возникающим при этом, а заодно, внесем ясность и в вопрос о том, обязательно ли нужно сбрасывать флаги 1-й группы (флаги Z, C, DC, которые мы рассматриваем, относятся к этой группе флагов) или нет, перед выполнением команд, которые на эти флаги воздействуют?
Если с операцией переноса все понятно (бит С установился в 1, то есть, флаг поднялся), то, в случае заема, флаг С, формально (так как мы "привязались" к тому, что флаг С считается поднятым если в нем "стоит" 1 и опущеным, если 0), наоборот - опустился.
Такой "привязкой" удобно пользоваться при переносе (случай суммирования), но, при заеме (случай вычитания), это как-то "неудобоваримо", и виной этому то, что заем имеет инверсное значение.
В этом случае (при применении команд SUBWF или SUBLW), нужно просто проинвертировать "привязку", то есть изменить критерии состояний флага С: флаг поднят - 0, флаг опущен - 1.
В соответствии с этим, вносим коррективы в проверочную группу команд (ранее предполагалось, что флаг С устанавливается в 1 и при переносе и при заеме): если производится суммирование, то нужно применить bcf Status,0, а если вычитание, то bsf Status,0, тогда на момент перехода рабочей точки программы на команду ADDLW или SUBLW, флаг С будет гарантированно сброшен с учетом выполняемой операции.
Что касается ответа на сформулированный выше вопрос (он выделен синим цветом), то ответ такой: нет, не обязательно, но, для начинающих, желательно.
Разбираемся: прочитайте определение флагов 1-й группы, которое дано выше.
Это и есть часть ответа.
И в самом деле, какой смысл предварительно сбрасывать все те же флаги Z, C, DC, если их состояния полностью определяются результатами исполнения соответствующих команд.
Проще говоря, в каком бы из двух состояний не находились эти флаги до момента исполнения соответствующих команд (которые на них воздействуют), после их исполнения, флаги будут установлены в состояния, однозначно соответствующие результатам выполненных операций и принудительно, предварительно их сбрасывать или устанавливать вовсе не обязательно.
Таким образом, из группы команд, реализующих цифровой компаратор ("программы" flag.asm и flag_1.asm) и группы проверочных команд, работающих с флагами C и DC, можно убрать команды bcf Status,Z и bcf Status,0 (для суммирования), bsf Status,0 (для вычитания).
Так что же делать: убирать или оставлять команды предварительных сбросов флагов?
Ответ: если изложенная выше информация Вам предельно ясна и понятна, и Вы имеете, хотя бы небольшой, опыт составления и отладки программ, то команды предварительных сбросов флагов можно не применять, а если Вы начинающий программист, то отказываться от них не стоит по причине того, что, в этом случае, процесс отладки программ, в части касающейся отладки подпрограмм, в которых происходят опросы состояний флагов 1-й группы (об этих опросах я расскажу ниже), становится для программиста более наглядным и комфортным.
Например:
предварительного сброса флага С нет, а на момент исполнения команды ADDLW, в результате исполнения предшествующих команд программы, бит флага С установился в 1.
Допустим, суммируем числа .100 и .200, следовательно, после исполнения команды ADDLW, бит флага С должен установиться в 1.
Но в нем и так "стоит" 1 и, в результате исполнения команды ADDLW, состояние бита флага С не изменится (подтверждение ранее установленного состояния).
Хотя, в этом случае, работа программы и не нарушится, но в окнах симулятора Вы не увидите изменения состояния бита флага С, что, для начинающих, не совсем комфортно (затруднение отладки и отслеживания программы), и особенно тогда, когда нужно получить ответ на вопрос: "В каких случаях работы программы сумма двух чисел, после исполнения команды ADDLW, больше числа .255?"
При наличии такого рода затруднений при суммировании, целесообразно предварительно установить бит флага С в ноль (bcf Status,0) так, как это сделано в группе проверочных команд, после чего можно будет с комфортом наблюдать за поднятием флага в окнах RAM или SFR.
Если нужно проконтролировать случаи сбросов флага (такого рода необходимость не так часта), то, перед исполнением команды ADDLW, в данном случае, можно либо вообще ничего не "вставлять", либо предварительно поднять флаг С командой bsf Status,0 (по принципу: "на всякий случай, хуже от этого не будет, зато увижу изменения").
Подобные манипуляции можно проделать и при работе с другими флагами 1-й группы.
Может быть, опытные программисты и сочтут подобного рода рассуждения "переливанием из пустого в порожнее" и посчитают такое внимание к контрольным (второстепенным) операциям излишним, но я так не считаю по той простой причине, что опытные программисты это знают, а начинающие - нет, а ведь именно осмысление подобного рода "тонкостей" и дает то, что называется профессиональной уверенностью.
Так как начинающим, на первых порах, такого рода многовариантность противопоказана, могу посоветовать следующее:
при работе с флагами Z, C, DC, всегда, в описанных выше случаях, предварительно их сбрасывайте до тех пор, когда операции с этими флагами не будут вызывать у Вас никаких затруднений и только после этого отказывайтесь от применения предварительных операций сбросов флагов.
Почему именно предварительно сбрасывать флаги, а не поднимать их?
Потому что, в подавляющем большинстве случаев, "индикатором значимого события" является поднятие флага, что соответствует:
- нулевой результат: при поднятии флага нулевого результата, бит Z устанавливается в 1,
- перенос: при поднятии флага переноса, бит С устанавливается в 1,
- заем: при поднятии флага заема, бит C устанавливается в 0 (заем имеет инверсное значение).
Примечание: при предварительном сбросе флагов Z и С(при суммировании), их биты должны устанавливаться в 0, а при предварительном сбросе флага C (при вычитании), его бит должен устанавливаться в 1 (еще раз повторяю: заем имеет инверсное значение).
При соблюдении этих требований, Вы всегда будете иметь возможность визуально контролировать (отслеживать) в окнах симулятора RAM и SFR все изменения типа "нулевой результат", "перенос" и "заем", не опасаясь совершения операций типа "подтверждение ранее установленного состояния", которые затрудняют такой контроль.
Перед тем, как "поторошить" флаг DC, внесу некоторую ясность в вопрос о том, почему арифметические операции сложения и вычитания выполняются в ПИКах так быстро?
Или этот вопрос можно сформулировать так: "Почему флаги С и DC так быстро "реагируют" на результаты исполнения арифметических операций?" Все познается в сравнении и поэтому приведу пример "доисторического" 4-хбитного арифметического устройства на двоичном счетчике с предустановкой 555ИЕ7 (полный цикл счета - 16 чисел).
Например, нужно сложить числа .02 и .10.
Код числа .02 подается на входы предустановки и записывается в триггеры счетчика.
После этого, на счетный вход счетчика подается 10 тактовых импульсов.
Счет начинается от числа .02 и прекращается на числе .12.
Это - пример арифметического устройства последовательного типа, которое работает "долго и нудно".
И в самом деле, ждать результата 10 тактов - "радость небольшая", а если этот "динозавр", например, многокаскадный и второе слагаемое - большое число?
Такие вычисления могут понравиться только любителям перекуров.
Для того чтобы произвести эти вычисления всего за время исполнения одной команды (за 1 машинный цикл ПИКа, равный 4-м тактам опорного генератора), в микроконтроллерах используются так называемые 8-разрядные полные сумматоры.
Кто этим интересуется: информации о них полным - полно в технической литературе широкого пользования.
Почему именно "сумматор" - понятно, так как он осуществляет операцию суммирования. А как быть с операцией вычитания, ведь сумматор может только складывать?
Дело в том, что, применяя, при выполнении операции вычитания, специальные приемы (преобразования чисел), можно свести операцию вычитания к операции сложения.
В этом случае, операция вычитания является специфической разновидностью операции сложения, и при помощи полного сумматора можно реализовать обе эти операции, что и имеет место быть.
Как конкретно это происходит - программисту знать вовсе не обязательно, но если кто интересуется, то литературы на этот счет много.
8- разрядный полный сумматор работает в стандартном параллельном весовом (обычном) коде и имеет 2 группы входов, по 8 разрядов каждая, на которые подаются слагаемые числа, 8 выходов, на которых отображается сумма этих чисел и один выход переноса - заема.
Никаких дополнительных сигналов управления для запуска "механизма" суммирования, в этом случае, не нужно, так как, проще говоря, полный сумматор суммирует, "не спрашивая разрешения".
Какие числа "стоят" на входах, такие он тут же и суммирует.
Просто необходимо в нужный момент их "выставить", и сразу же после этого (буквально после паузы в несколько десятков наносекунд) можно считывать результат (он будет зафиксирован в течение всего времени фиксации слагаемых чисел), то есть арифметические операции можно совершать быстро, всего за 1 такт (1 машинный цикл).
А теперь - важный момент: состояния бита флага С (DC) есть состояния выхода переноса - заема такого 8-разрядного полного сумматора.
Программист, единственное что может, так это только проконтролировать состояние выхода переноса - заема (то есть, состояния флагов С и DC) или произвести его опрос программными средствами с целью направления рабочей точки программы в один из двух сценариев.
Разрядность этого полного сумматора программист нарастить не может. Он производит операции только в пределах одного байта. После завершения операции, байт результата должен быть скопирован в регистр общего назначения, иначе, при исполнении следующей операции, он будет "потерян".
Наращивание разрядности чисел, участвующих в арифметических операциях и чисел результатов арифметических операций производится путем задействования соответствующего количества регистров общего назначения и применения соответствующих команд.
Возникает вопрос: "Какой практический толк от этих флагов? Ну они опускаются или поднимаются, а дальше что? Они же ни на что не влияют, и от их состояний ход исполнения программы никак не зависит."
Ответ: если необходимо получить зависимость хода исполнения программы от фактов возникновения переносов или заемов, программист может, программными средствами, опросить состояния флагов С или DC и, в зависимости от этих состояний, "направить" рабочую точку программы в один из двух сценариев ее исполнения (также, как и в случае задействования флага Z).
Подобного рода манипуляции мы уже проделывали и "Америку здесь открывать не нужно".
Правда, есть определенная специфика, о которой я расскажу позднее.
А пока продолжим тему флагов.
"Пытаем" флаг DC.
"Орудия пытки" - такие же, как и при "пытке" флага С, только контролировать нужно не нулевой бит регистра STATUS, а первый его бит.
Уберите из текста программы Multi проверочную группу команд для флага С, "вставьте" на ее место проверочную группу команд для флага DC и далее нужно работать по принципу работы с флагом С.
Специфика работы с флагом DC такова, что лучше сначала прочитать пояснения, а потом начать с ним работать.
Байт состоит из 2-х полубайтов по 4 бита в каждом.
Они разделяются на младший и старший полубайты. Младший полубайт тот, в состав которого входит нулевой бит байта.
Так как в каждом полубайте по 4 бита, то младший полубайт способен отобразить 16 чисел (от .00 до .15).
А теперь проведите аналогию между байтом, в случае работы с флагом С, и младшим полубайтом, в случае работы с флагом DC.
Все - один к одному, разница только в том, что, в первом случае (флаг С), переносы или заемы происходят при "выходе" результата арифметической операции за пределы числового интервала от .00 до .255, а, во втором случае (флаг DC), - за пределы числового интервала от .00 до .15.
Соответственно, в 1-м случае, любое из двух значений чисел, участвующих в арифметических операциях, не должно превышать числа .255, а во 2-м случае, не должно превышать числа .15.
При работе с флагом DC, перенос осуществляется в младший разряд старшего полубайта, а не в дополнительно задействованный регистр общего назначения, как в случае наращивания разрядности при работе с флагом С.
Исходя из того, что флаг DC работает только с младшим полубайтом, входящим в состав байта, то можно сделать следующий вывод: при операциях с числами, значения которых способен отразить байт (.00 - .255), флаги С и DC будут работать в комплексе.
Давайте посмотрим, как это будет выглядеть практически.
"Привяжемся" к операции суммирования в пределах одного байта.
Давайте что-нибудь посчитаем с использованием все той же группы проверочных команд для случая суммирования (см. выше).
Можете убрать из нее команду bcf Status,0 по той простой причине, что, при работе этой группы команд в составе программы Multi, перед исполнением команды addlw Y, биты С и DC сброшены (флаги С и DC опущены) и эта команда (bcf Status,0) просто не нужна (это как раз то, о чем говорилось выше).
Оставшиеся 2 команды "вставьте" в текст программы Multi на свое "проверочное место", так, как Вы это делали ранее.
На место констант X и Y поставьте нули (начало анализа).
По ходу работы можно производить и одновременную смену констант, но, для того чтобы не запутаться, лучше одну из этих констант, для начала, зафиксировать (.00), например, X, а константу Y постепенно увеличивать, начиная с числа .00.
Основное внимание - на нулевой и первый биты регистра STATUS.
Откройте окна RAM или SFR, кому какое удобнее, а можно и оба.
Откройте также окно Watch, "заложив" в него регистр W(в нем Вы будете видеть результат суммирования).
Не забывайте после каждой смены значения константы производить ассемблирование.
Напоминаю: используемая нами команда ADDLW "оказывает влияние" на флаги Z, C, DC (см. распечатку команд).
"Встаньте" на команду movlw .00. Это означает, что последней была выполнена команда bcf Status,5.
Посмотрите на содержимое регистра STATUS. В нем - число 18h (0001 1000).
Разбираемся: биты флагов -PD (3-й бит) и -TO (4-й бит) установлены в 1.
Причина: после ассемблирования (что эквивалентно включению питания), ПИК сбрасывает сторожевой таймер WDT, то есть, автоматически исполняется команда CLRWDT (см. распечатку команд).
Почему при сбросе WDT 3-й и 4-й биты устанавливаются в 1, а не в 0?
Обратите внимание на дефис перед названием этих флагов: он означает инверсию.
Таким образом, флаги -PD и -TO опущены (установлены в 1).
Далее, на эти флаги можно не обращать внимания.
Флаги С и DC также опущены (установлены в 0).
Производим суммирование двух нулей, последовательно выполняя команды movlw .00 и addlw .00.
После этого, содержимое регистра STATUS изменится с 18h на 1Ch (0001 1100).
Все правильно, 0+0=0, флаг нулевого результата Z (2-й бит) поднялся.
В команде ADDLW меняем .00 на .01. Ассемблируем. Сбрасываем программу на начало.
В пошаговом режиме доходим до команды addlw .01 и исполняем ее.
Смотрим на содержимое регистра STATUS.
В нем опять число 18h (0001 1000).
Вывод: так как результат суммирования не равен 0, флаг нулевого результата Z сбросился, переносов нет, так как значение суммы не выходит "за границы (см. выше) рабочих зон".
Точно такая же "картина" будет наблюдаться, если Вы будете, в качестве числа Y, будете использовать любое число до .15 включительно.
При этом, значение числа суммы не превысит .15 и переполнения младшего полубайта не будет.
Суммируем .00 и .16.
Сумма равна .16 (число большее, чем способен отобразить младший полубайт и, казалось бы, флаг DC должен подняться).
Смотрим на содержимое регистра STATUS.
В нем - 18h (0001 1000). Почему не поднялся флаг DC?
Посмотрите на младший полубайт числа .16. Он равен .00 (0000), а 0+0=0, то есть, "выхода за границы" младшего полубайта нет и флаг DC, по определению, подняться не должен.
Возникает еще один вопрос: почему, когда мы раньше суммировали нули, флаг нулевого результата Z поднялся, а теперь он не поднялся?
Ответ: флаг Z работает с байтом, а .00+.16=.16 - не нулевой результат (в отличие от .00+.00=.00).
Точно так же флаг DC (а также флаги C и Z) не поднимется и в тех случаях, если Вы будете, вместо числа Y, "подставлять" любые другие числа, большие чем .16 (вплоть до .255).
Можете поменять местами числа X и Y, результат будет тот же самый.
Учитывая то, что флаг DC не поднимался и тогда, когда вместо числа Y "подставлялись" числа от .00 до .15, можно утверждать следующее:
Если одно из слагаемых (или оба) является нулем, то какое бы из 8-разрядных чисел (в двоичном исчислении) не являлось вторым слагаемым, флаги C и DC никогда не поднимутся, а флаг Z поднимется только в случае .00+.00=.00.
На основании этого утверждения можно сделать следующий практический вывод: если одно из слагаемых равно нулю, то переносов происходить не будет.
В сущности, это объясняется просто: в этом случае (одно слагаемое равно 0), из-за того, что другое слагаемое, по определению, не может быть больше числа .255 (для байта) и .15 (для полубайта), "выхода за границу зоны" отображаемых байтом и полубайтом чисел, не происходит, следовательно, не происходит переносов, и флаги C и DC будут опущены.
А теперь замените .00 на .01, а вместо .16 поставьте .15.
Сумма - такая же, как и в случае .00+.16=.16. Посмотрим, что изменится.
После выполнения команды ADDLW, в регистре STATUS зафиксируется число 1Ah (0001 1010).
Теперь флаг DC поднялся.
Он также поднимется и в случаях 2+14, 3+13, 4+12 и т.д. до 15+1 включительно и при других комбинациях 8-мибитных чисел, если ни одно из них не является нулем и их сумма больше 15-ти, а, если меньше 15-ти (в том числе и в случаях "применения" нулей), то флаг DC будет сбрасываться.
А теперь посмотрим, что произойдет, если слагаемые "выходят" из числового диапазона полубайта (.00 - .15) в других случаях?
Например, просуммируем числа .08 (0000 1000) и .150 (1001 0110).
Флаг DC сбросится. Почему?
Еще раз напоминаю, флаг DC работает только в пределах младшего полубайта.
Младший полубайт числа .08 равен .08. Младший полубайт числа .150 равен .06.
Флаг DC сбросился потому, что .08+.06=.14 (меньше .15).
А теперь просуммируйте числа, например, .08 (0000 1000) и .153 (1001 1001).
Флаг DC поднимется. Почему?
Младший полубайт числа .08 равен .08. Младший полубайт числа .150 равен .09.
Флаг DC поднялся потому, что .08+.09=.17 (больше .15).
А вот пример, когда оба флага (C и DC) поднимаются: просуммируйте числа, например, .139 (1000 1011) и .157 (1001 1101).
Флаги С и DC поднимутся. Почему?
Младший полубайт числа .139 равен .11. Младший полубайт числа .157 равен .13
Флаг DC поднялся потому, что .11+.13=.24 (больше .15).
Флаг С поднялся потому, что .139+.157=.296 (больше .255).
После исполнения команды ADDLW, в регистр W будет записано число 28h (.40).
Разбираемся, откуда оно "взялось".
Из-за того, что число .296 больше числа .255, произошел переход на второй "виток" полного цикла счета.
В пределах этого второго "витка", результат суммирования "займет положение" числа .296-.256=.40 (28h).
Обращаю Ваше внимание: оба этих "витка" происходят в одном и том же байте (регистре) и количество этих "витков" не может быть более двух (самостоятельно ответьте на вопрос - почему?).
Вернемся к флагу DC.
Таким образом, вне зависимости от значения суммы двух чисел, отображаемой байтом (.00 - .255), флаг DC "реагирует" только на результаты выполнения операций сложения (вычитания) содержимого младших полубайтов.
Образно выражаясь, флагу DC "все-равно", что происходит в старшем полубайте, он работает на своем узком участке в 4 бита и "контролирует" именно этот участок, а не весь байт. Весь байт (оба полубайта) "контролируют" флаги Z и C.
Возникает вопрос: "А где же находится этот "легендарный" байт, с которым мы все время работаем?" Ответ: там, куда программист посчитает целесообразным его поместить.
В данном случае, это байт регистра W (см. комментарий к команде ADDLW). Именно в регистр W (аккумулятор) записывается результат выполнения операции сложения, и именно на этот результат "реагируют" флаги Z, C, DC в случаях применения команд, которые "влияют" (задействуют) на эти флаги.
Если программист применит байт - ориентированную команду ADDWF, то он даже сможет выбрать, куда поместить результат операции (байт): или во все тот же аккумулятор, или в предварительно назначенный им регистр общего назначения (а это уже выход на "широкий оперативный простор").
Так что, байт, с которым мы работаем не есть что-то "виртуальное". Его можно "пощупать" в симуляторе и проделывать с его содержимым различные манипуляции.
Вернемся к флагу DC.
Посмотрите в комментарий к флагу DC (см. распечатку).
В нем флаг DC называется флагом десятичного переноса - заема.
Этот комментарий я, в свое время, взял из технической документации, предоставляемой компанией Microchip.
Честно говоря, "привязки" этого флага к десятичной системе исчисления я не обнаружил.
Он "привязан" только к двоичной системе исчисления (также, как и остальные флаги).
Так что, слово "десятичного", из текста комментария лучше либо убрать, либо заменить на слово "двоичного".
Мне кажется, что это либо опечатка, либо небрежность при написании комментария.
Если кто-то обнаружит "привязку" флага DC к десятичной системе исчисления, то прошу Вас написать об этом мне.
Исходя из сказанного выше, флаг DC может применяться только для контроля типа "перенос - заем есть/нет" в "зоне" 4-хбитных чисел (.00 - .15).
Каким-то образом "вычленить" эти 4 бита (младший полубайт) из байта и работать с ними отдельно, технически невозможно, так как все рабочие регистры ПИКов 8-битные, и, хочешь или не хочешь, то, что получается на "выходе" - всегда 8-битное число.
Поэтому, при анализе "реакции" флага DC на результат соответствующей операции, приходится из 8-битного результата "вычленять" 4-хбитное число, по образу и подобию того, как это было проделано выше, и только после этого можно ответить на вопрос: "Почему флаг DC поднялся или опустился?"
Это, конечно, не очень удобно, но что поделаешь...
Если суммируются 8-битные числа из числового диапазона от .00 до .15 (нулевые старшие полубайты), то - никаких проблем.
Утешает то, что практическая надобность в задействовании флага DC очень мала (по сравнению с другими флагами), так как операции с 4-хбитными числами, в ограниченном диапазоне чисел от .00 до .15, очень редки.
К тому же, если и возникает такая необходимость, то функции флага DC можно "передать" флагу С, зафиксировав в старшем полубайте одного слагаемого число .15 (1111), а в старшем полубайте другого слагаемого число .00 (0000).
Конечно, возможность такой "передачи" нужно рассматривать в каждом конкретном случае, так как она не всегда имеется.
На практике, в подавляющем большинстве случаев, когда возникает необходимость анализа фактов переноса или заема, происходит работа с флагом С.
А теперь давайте остановимся, "переведем дух" и постараемся понять смысл того, что Вы прочитали.
У многих из Вас может возникнуть вопрос: "А зачем вообще нужно было так подробно "расписывать" этот процесс? Зачем создавать "перенапряг". Дал бы готовое определение каждого флага и дело с концом."
Ответ: при таком подходе к качеству воспринимаемой информации, программист рискует оказаться один на один с множеством неразрешимых для него задач, не будучи в состоянии их решить, даже при сильнейшем желании это сделать.
Таких задач, по ходу составления и отладки программы, может быть много и если не владеть эффективной и "агрессивной" методикой их решения (типа "Я тебе покажу кузькину мать!"), то участь такого программиста печальна.
Какой смысл взращивать в себе комплекс неполноценности по отношению, например, к все тем же флагам C и DC, если, пользуясь элементарной "уловкой" и имея в своих руках такой мощнейший "инструмент", как MPLAB, можно "выпотрошить" эти флаги от и до и выяснить всю их "подноготную", не надеясь, что кто-то вдруг возьмет, да и осчастливит.
Главная польза такого рода "взломов" даже не в том, что в их результате "добывается" нужная информация, а в том, что эта информация "уходит в подкорку" в виде индивидуальных для каждого программиста образов, которые, в нужное время, извлекаются из подсознания в виде готового (или близкому к этому) решения, которое не нужно каждый раз "вымучивать" или "рождать при помощи кесарева сечения".
Для того чтобы такое "извлечение" происходило в "автомате", нужно, конечно, основательно "попотеть" и провести в симуляторе многие и многие часы, производя какие-то осознанные, целенаправленные действия с "уходом" в заблуждения и "выкарабкиванием" из них.
Примерно такого рода процесс я и попытался примитивно воспроизвести для затруднительной ситуации типа "Как работают флаги C и DC?"
Этот пример также можно рассматривать как примитивный пример работы хакера.
Вывод: "не стоит ждать милостей от природы". Если программист хочет добиться чего-нибудь стоящего, то ему нужно не ждать "бесплатного сыра", а, со знанием дела, брать в руки "интеллектуальную дубину" и выбивать "всю дурь" из своих проблем и затруднений.
Славянам не нужно объяснять, как с ней обращаться, лишь бы она была в наличии, да потяжелее, плюс хорошие мозги: вот так и рождаются настоящие классные программисты, которые так ценятся на Западе.
Свою главную задачу я вижу не в том, чтобы заниматься подробным описанием этой "дубины" - всей моей жизни для этого не хватит, да я и не считаю себя человеком, который ее имеет (чем богаты, тем и рады), а в том, чтобы описать хотя бы "палку" c тем, чтобы в дальнейшем, каждый из Вас смог "превратить" ее в "дубину" тех или иных "габаритов", и, что самое главное, управляемую людьми, отдающими себе отчет в том, что они делают.
Конец "лирического отступления". Пошли дальше.
Теперь - поближе к практике.
Пример использования флага C Вы найдете в следующем разделе.
Переходите к следующему разделу.


Kea_34

14. Пример задействования флага С в трехбайтном суммирующем
устройстве. Циклический сдвиг. Операция умножения.

Пример реализации стандартного двоичного 3-байтного суммирующего устройства
 Как Вы уже должны были заметить, составляя любую программу, программист всегда работает с тем или иным количеством регистров общего назначения.
В них закладываются константы, с их содержимым производятся различные операции, в них записываются результаты операций, из них производится считывание чисел для дальнейшего использования их в работе программы.
Все они - однобайтные и, в пределах одного байта (числа от .00 до .255), работа с ними не вызывает особых затруднений.
На практике же, очень часто, возникает необходимость работы с числами, величины которых гораздо больше, чем .255.
Например, частотомер, который я предлагаю для повторения на главной странице моего сайта, содержит 8 десятичных разрядов, что соответствует максимальному значению отображаемого десятичного числа 99 999 999.
В двоичном виде это число выглядит так: 101 1111 0101 1110 0000 1111 1111 (можете убедиться в этом, заглянув в программку конвертора систем исчисления).
Итого: 27 двоичных разрядов.
Для того, чтобы отобразить такое число, трех регистров окажется мало (8*3=24 разряда), следовательно, нужно задействовать 4 регистра общего назначения (8*4=32 разряда).
При этом получается "перебор" в 5 разрядов, но, во-первых, "кашу маслом не испортишь" (эти 4 регистра способны отобразить число гораздо большее, чем 99999999), а во-вторых, выбора все-равно нет, так как все регистры 8-битные.
Вопрос: как распределить эти разряды по регистрам?
Ответ: необходимо распределить приоритеты старшинства.
Вспоминайте о том, о чем говорилось ранее (HH, H, M, L).
Назначаем и "прописываем" регистры, например, с названиями RegHH, RegH, RegM, RegL и "закрепляем" за каждым из них по одному байту 4-байтного (32-битного) двоичного числа, начиная с младшего (он будет "лежать" в регистре RegL) и далее, по-порядку, вплоть до самого старшего (он будет "лежать" в регистре RegHH).
Таким образом, мы по частям распределили 4-байтное двоичное число по 4-м регистрам общего назначения в строго определенном порядке, с целью дальнейшей "привязки" к этому порядку при дальнейшей работе.
Таким образом, на стадии подготовки к написанию текста программы, количество регистров, в такого рода группе регистров, назначается по принципу "перекрытия максимально возможного числа" (в нашем примере: нужно 27, а организовано 32 разряда - единственно возможный оптимальный вариант).
Например, в случае применения 7-разрядного 7-сегментного индикатора, достаточно и трех регистров (RegH, RegM, RegL).
А теперь давайте осмыслим, что мы "сотворили". Мы раздробили 4-байтное число на 4 части, нарушив тем самым внутренние связи между байтами.
Проще говоря, имеются 4 отдельных части, которые между собой не взаимодействуют.
Следовательно, нужно каким-то образом восстановить разрушенные связи.
Необходимость наличия таких связей можно объяснить на таком примере:
имеется какое-нибудь рабочее устройство на микросхеме 8-разрядного регистра (ИР).
Требуется собрать то же самое, но на 2-х микросхемах 4-разрядных регистров.
Для этого нужно соответствующим образом соединить эти 2 микросхемы, ведь без такого соединения устройство просто не будет работать.
Примерно то же самое нужно сделать и в описанном выше случае, разница заключается только в том, что, в одном случае, речь идет об аппаратном способе соединения, а в другом случае, о программном способе соединения.
Вот ради чего и затеивались все эти "разборки" с переносами, заемами и флагами C и DC.
Таким образом, при помощи флагов С, DC, а также и флага Z, можно организовать "полноценное" взаимодействие байтов в пределах многобайтного числа.
То же самое можно интерпретировать и по-другому: при помощи флагов C, DC, а также флага Z, из нескольких регистров общего назначения можно создавать N-байтные регистры (N - количество задействованных регистров общего назначения).
Таким образом, создавая многобайтные регистры общего назначения, можно производить операции с очень большими, по величине, числами.
Специфика работы таких регистров состоит в том, что многобайтный регистр "рождается" только тогда, когда программа этого "захочет", то есть, в том случае, когда совершаются соответствующие операции с содержимым его составных частей.
В этом случае "включаются" взаимные связи и регистр начинает работать как многобайтный.
В остальных случаях это просто отдельные регистры.
Давайте разберемся, как, при помощи программных средств, организуется такой многобайтный регистр, и как он работает, например, в трехбайтном суммирующем устройстве, файлы с примерами которых Вы можете скачать ниже:

Файл c_dc.asm (формализованный вид): здесь.

Файл c_dc_1.asm (неформализованный вид): здесь.

Проект под них создавать не нужно, откройте их в MPLAB как обычные файлы.
Вы видите перед собой "вырезку" из программы частотомера - цифровой шкалы А.Денисова, представляющую собой стандартную подпрограмму, реализующую классическое трехбайтное суммирующее устройство.
В данном случае, при помощи этого устройства, осуществляется суммирование 3-байтного значения измеренной частотомером частоты и 3-байтной поправки, которая представляет собой значение установленной пользователем (или по умолчанию) промежуточной частоты.
Вспомните первый случай вычисляемого перехода, который был рассмотрен ранее.
Одной из команд этого вычисляемого перехода и является команда goto PLUS.
Таким образом, при наличии определенного состояния клавиатуры (после ее опроса), происходит безусловный переход в ПП PLUS, в которой и осуществляется суммирование значений результатов измерения частоты и константы, определяющей значение промежуточной частоты.
Перед "разбором" подпрограммы, следует кое-что уяснить.
Напоминаю, что микроконтроллер "понимает" только двоичные числа.
То, что в тексте программы могут указываться числа в десятичной системе исчисления (в рассматриваемой "программе" этого нет, но в ранее рассмотренных - есть) - всего-лишь элемент удобства, предоставляемого программисту разработчиками MPLAB, и это не свидетельствует о том, что производятся операции с десятичными числами.
В конечном итоге, MPLAB автоматически преобразует их в двоичные числа, с которыми и работает ПИК.
Если возникает необходимость перевода чисел из двоичной в двоично-десятичную систему исчисления, то, сначала, нужно, в полном объеме, произвести соответствующие операции с двоичными числами, вплоть до получения двоичного числа результата, и только после этого переводить его в двоично-десятичную форму с последующим кодированием, например, с целью отображения десятичного числа в 7-сегментном индикаторе.
Как осуществляется такое кодирование, было рассмотрено ранее (второй пример вычисляемого перехода), а как осуществляется перевод двоичных чисел в двоично-десятичные - будет рассмотрено позднее.
Сейчас же будет показано, как формируется трехбайтное двоичное число, отображаемое (после преобразования его в двоично-десятичную форму с последующим кодированием) в линейке из 7-ми 7-сегментных индикаторов (диапазон отображаемых чисел от 0 до 9999999) частотомера - цифровой шкалы (работа в подрежиме "плюс ПЧ" режима цифровой шкалы) или какого-то другого устройства, в состав которого эта линейка может входить.
Фактически, рассматриваемая подпрограмма состоит не из одной, а из двух подпрограмм (PLUS и PLUS_1).
Подпрограмма PLUS_1 находится внутри подпрограммы PLUS, следовательно, если речь идет о ПП PLUS, то имеются ввиду все команды подпрограммы, включая и команды ПП PLUS_1.
А теперь ближе к делу.
Вспомните то, о чем говорилось в разделе посвященному работе с EEPROM - памятью.
В ее ячейки, предварительно, можно записывать какие-нибудь числа, которые затем используются в работе программы.
В рассматриваемом случае, в 3 ячейки EEPROM - памяти записывается, предварительно вычисленное программистом, трехбайтное (24-битное) двоичное число, которое отражает величину значения промежуточной частоты по умолчанию.
Естественно, при этом "расставляются" приоритеты старшинства.
При "запуске" программы (после включения питания), числа из этих ячеек, также с соблюдением приоритетов старшинства, копируются в регистры RegH, RegM, RegL (см. текст "программы").
В регистрах TimerH, TimerM, TimerL, на момент начала исполнения ПП PLUS, находится трехбайтное двоичное число, отражающее результат подсчета значения измеряемой частоты на текущем полном цикле программы.
Суммирование в ПП PLUS происходит побайтно, начиная с младших байтов, то есть, сначала производится суммирование содержимого регистров RegL и TimerL, затем RegM и TimerM, а затем RegH и TimerH.
После выполнения команд сложения, производится опрос состояния флага С, по результатам которого делается вывод о наличии или отсутствии необходимости инкремента содержимого следующего по разрядности регистра Timer.
Критерий этого очень простой: превышение или нет значения суммы, "границы" числового диапазона байта (числа .255).
Если такой инкремент происходит, то это означает, что осуществлен перенос в следующий по старшинству разряд трехбайтного регистра TimerH/TimerM/TimerL.
При наличии этого переноса, значение байта, следующего по старшинству регистра TimerM(H), сначала увеличивается на 1, а затем, это увеличенное на 1 число, суммируется с таким же по старшинству байтом регистра Reg (RegM(H)).
После выполнения команд сложения, его результат сохраняется все в тех же регистрах Timer (c "привязкой" к соответствующим разрядам L, M, H).
Обращаю Ваше внимание, что инкремента младшего байта (переноса в младший байт), находящегося в регистре TimerL, никогда не происходит по той простой причине, что он младший и переносить в него нечего.
Таким образом, в данном случае, перенос может происходить только из младшего разряда 3-байтного числа L в средний разряд 3-байтного числа M и из среднего разряда 3-байтного числа M в старший разряд 3-байтного числа H.
Программист должен рассчитать количество разрядов многобайтного регистра таким образом, чтобы, при работе устройства, был невозможен выход результата сложения содержимого старших байтов за пределы числового диапазона байта (сумма, сохраненная в регистре TimerH, не должна превышать числа .255), в противном случае, результат суммирования будет отражаться на втором "витке" полного цикла счета (неверное отражение результатов подсчета).
Например, 3-байтный регистр сможет безошибочно отобразить числа от 0 до 256*256*256-1=16777215.
Если нужно отобразить результат суммирования больший, чем 16777215, то нужно организовывать 4-байтный регистр (добавить регистрыRegHH и TimerHH) и, соответственно, добавить в подпрограмму PLUS команды, организуюшие суммирование содержимого регистров RegHH и TimerHH и перенос из разряда H в разряд HH.
Снизу от директивы end Вы видите "арифметические примеры суммирований".
Пример № 1: случай отсутствия переносов.
В этом случае, связи между байтами отсутствуют (переносов нет), и просто осуществляется последовательные, побайтные суммирования в пределах пары одноименных байтов (L+L, M+M, H+H) регистров Reg и Timer, начиная с младших байтов (RegL+TimerL).
Результаты анализов состояний битов C и Z - нулевые, в результате чего, рабочая точка программы "обходит стороной" все команды инкрементов.
Пример № 2: случай наличия переносов в разряды M и H при отсутствии нулевого результата операций суммирования.
Побайтное суммирование начинается с младших байтов (L) регистров Reg и Timer.
В результате этого суммирования, происходит "выход" на второе кольцо 8-битного полного цикла чисел (.44) и флаг С поднимается.
После опроса состояния флага С, рабочая точка программы уходит в сценарий "инкремент содержимого регистра TimerM", что и есть перенос, после чего происходит суммирование байтов средних разрядов (M) регистров Reg и Timer.
В результате этого суммирования, происходит "выход" на второе кольцо 8-битного полного цикла чисел, с учетом предшествующего декремента содержимого регистра TimerM (.45), и флаг С снова поднимается.
После еще одного опроса флага С, рабочая точка программы уходит в сценарий "инкремент содержимого регистра TimerH", что также есть перенос, после чего происходит суммирование байтов старших разрядов (H) регистров Reg и Timer.
Все, процесс суммирования завершен.
После этого (а также и в других случаях) исполняется команда goto YES и рабочая точка программы "покидает" ПП PLUS для того чтобы далее "делать какие-нибудь полезные дела".
Если предположить, что ПП PLUS входит в состав программы частотомера - цифровой шкалы, то, после выхода рабочей точки программы из ПП PLUS, происходит ее безусловный переход в подпрограммы преобразования двоичных чисел в двоично - десятичные.
Пример № 3: случай наличия переносов в разряды M и H при наличии нулевого результата операции суммирования (слагаемые - не нулевые).
Его разница с примером № 2 заключается в том, что при суммировании чисел, отображаемых средними байтами (M) регистров Reg и Timer, "выход" на второе кольцо 8-битного полного цикла чисел происходит на число, равное нулю (ноль второго "витка" цикла чисел).
В этом случае, поднимается флаг нулевого результата Z и перенос в старший разряд 3-байтного числа (H), то есть, инкремент содержимого регистра TimerH, осуществляется после опроса состояния флага Z, а не флага С.
Сразу возникает вопрос: а какова надобность задействования для переноса флага нулевого результата Z, ведь можно опросить все тот же флаг переноса - заема С?
Для ответа на этот вопрос вспомним то, с чем мы разбирались ранее: если хотя бы одно из слагаемых будет равно нулю, то флаг С никогда не поднимется.
Разберем следующий вариант суммирования с задействованием, в одном из слагаемых, нулей.
150 255 255
0 0 1
151 0 0
С суммированием младших байтов (L) регистров Reg и Timer - никаких проблем: .255+.01=.256=.00 плюс перенос (инкремент) в регистр TimerM (вспомните, как мы складывали .15 и .01, когда "поторошили" флаг С).
После инкремента содержимого регистра TimerM (.255+.01=.256=.00) и последующего сложения значений байтов среднего разряда регистров Timer (.00) и Reg (.00), получаем .00+.00=.00. Переноса нет.
А где перенос в старший байт (Н) регистра Timer?
Ответ: после совершения этой операции суммирования, флаг С поднят не будет, так как слагаемыми являются нули (то, о чем говорилось ранее, когда мы разбирались с флагом С), следовательно, переноса в старший байт (H) регистра Timer не будет, но он должен быть, так как результат суммы положительных чисел должен быть не меньше, чем величина любого из слагаемых.
В этом же случае (переноса нет), он будет меньше величины первого слагаемого (150 255 255) именно потому, что переноса не было.
Вывод: в данном случае, при задействовании для переноса флага С, критерий переноса отсутствует и перенос в следующий по старшинству байт не может быть осуществлен.
Выход из положения: задействование для переноса флага Z.
В этом случае, критерий переноса в наличии, так как суммируются нули, а сумма нулей есть ноль, то есть флаг Z поднимется, после чего производится "штатный" инкремент и все ОК.
Посмотрите, как это сделано в ПП PLUS.
Собственно говоря, как она работает я уже рассказал, а что недостает, Вы увидите в тексте "программы".
Предоставляется прекрасный повод для тренировки в составлении блок - схемы подпрограммы, и я советую Вам это сделать: проще будет "ориентироваться".
ПП PLUS является прекрасным образцом подпрограммы с достаточно "развитой" системой ветвлений, и кто ее "осилит", тот, несомненно, делает успехи.
Эту ПП, в дальнейшем, можно использовать в качестве "заготовки" при разработке программ под различные устройства, в работе которых используются аналогичные вычисления.
С непривычки, подобного рода подпрограммы могут показаться достаточно сложными, но, если разобраться, то ничего особо сложного в них нет, нужно просто определенное время "повариться в этом соку".
Именно по этой причине, а также по причине того, что стандартная подпрограмма вычитания сложнее стандартной подпрограммы сложения, я, пока, не считаю целесообраным производить "разбор полетов" стандартной подпрограммы вычитания.
Перед "штурмом этой вершины", нужно основательно разобраться с суммированием, причем, разобраться так, чтобы неясных вопросов не было.
Это обусловлено тем, что вычитание является специфической формой суммирования и программист, хорошо представляющий себе процесс суммирования многобайтных двоичных чисел, не встретит особых трудностей при "разборках" с вычитанием.
Так что, всему свое время, дойдет дело и до вычитания.
Теперь давайте разберемся с еще одной операцией, имеющей непосредственное отношение к биту флага С, а, заодно, и освоим еще две новые команды, но сначала подведем промежуточный итог.
Промежуточный итог
 Итак, тексты рассмотренных ранее "неполноценных" программ addwfpc.asm, c_dc.asm, flag.asm или тексты любых подпрограмм, не оформленных, как полноценные программы, можно "врезать" все в ту же "полноценную" программу Multi.asm (или в еще более простую программу) и "погонять" их в симуляторе "от души", выяснив все, что нужно.
Естественно, при этом необходимо проявить "изворотливость", скорректировав "шапку" программы и создав необходимые начальные условия для работы этих "вставок".
Лучше всего, специально под это дело, создать из программы Multi.asm программу, например, Multi_1.asm, в "шапке" которой "прописать оптом" все регистры специального назначения, определить место сохранения результатов операций (W equ 0, F equ 1), присвоить битам регистров специального назначения их названия, например, C equ 0, DC equ 1, Z equ 2, RBIE equ 3, INTE equ 4, RP0 equ 5, INTEDG equ 6, GIE equ 7.
Что касается последнего, то это один из вариантов, можно сделать и другой "расклад", но директивы C equ 0, Z equ 2, RP0 equ 5 лучше "не трогать", так как биты RP0, Z и C используются часто (особенно это относится к битам RP0 и Z).
Даже, если некоторые регистры специального назначения и некоторые их биты этого "оптового" списка использоваться не будут, то ничего страшного, это не является ошибкой.
После такой корректировки, нужно только прописать использующиеся регистры общего назначения, после чего можно переходить к рабочей части программы.
В ней нужно создать условия для работы "вставки", которые, в большинстве случаев сводятся к предварительной записи в использующиеся регистры констант, значения которых можно менять в соответствии с алгоритмом анализа.
После каждой такой смены, последующего ассемблирования и исполнения команд "вставки", программист анализирует результат и, в конечном итоге, выясняет алгоритм работы "вставки".
Конечно, все это не так просто, как может показаться на первый взгляд, так как, для того чтобы правильно симитировать начальные условия работы "вставки" и определить алгоритм ее анализа, нужно хотя бы иметь представление о возможных вариантах работы программы "до того".
Таким образом, речь идет о "раскрутке" по цепочке, начиная с его первого звена и далее - по порядку.
Важность такого рода работы очевидна, так как можно получить качественные ответы практически на все вопросы, связанные с работой групп команд или подпрограмм, не ожидая "милости от природы".
Кроме того, такая работа развивает аналитические способности, а по своей обучающей эффективности ей вообще нет равных.
Эти "страшилки" я рассказываю Вам вовсе не для того чтобы Вы "испугались" объема предстоящей работы (если есть "упертость", то это не помеха, а если нет, то тогда выгоднее "испугаться"), а для того, чтобы как-то обозначить наиболее эффективное ее направление.
Должно быть совершенно понятно, что эта работа не для лентяев (и даже самых одаренных), так как она связана с большими трудозатратами и, на первых порах, с большим количеством неудач, а как же иначе?
Дети рождаются в муках, а составленная программа - своеобразный "ребенок" - очень дорогой, любимый и приносящий много радости.
"Родами" займемся позднее, а пока продолжим "курс молодого бойца".
Циклический сдвиг
 Циклическим сдвигом "рулят" две команды: RLF (циклический сдвиг влево) и RRF (циклический сдвиг вправо).
Обе эти команды - байт - ориентированные, а это означает, что циклический сдвиг происходит внутри байта.
Посмотрите в распечатку команд. В ней, в строках команд RLF и RRF, Вы найдете блок - схему циклического сдвига влево и вправо.
Механизм сдвига - кольцевой. Старший или младший бит байта (в зависимости от направления сдвига) как бы "изымается" и "переносится" на противоположный "конец" байта через бит флага переноса - заема С.
В данном случае, работа флага С сводится к работе однобитного (не путайте с однобайтным) регистра, в который, при исполнении команды циклического сдвига, копируется значение старшего или младшего бита (в зависимости от направления сдвига) байта регистра, к которому обращается команда RLF или RRF.
А можно и "зайти с другого конца", как бы введя бит С в состав регистра, к которому обращается команда RLF или RRF с присвоением ему "статуса" бита № 8.
В этом случае, речь идет о составном (комплексном) 9-битном регистре, с раздельным отображением байта (биты с №№ 0 - 7) и бита с № 8, замкнутом в кольцо обратной связи.
Проще говоря, значение байта можно проконтролировать, просмотрев содержимое регистра, к которому обращается команда RLF или RRF, а значение бита с № 8 можно проконтролировать, просмотрев содержимое флага С.
Условием безошибочного проведения процедуры циклического сдвига является наличие, на момент начала (но не в ходе ее исполнения) этой процедуры, нуля в бите C.
Если до начала этой процедуры, команд воздействующих на флаг С (ADDWF, ADDLW, SUBWF, SUBLW) не применялось, то об установке в 0 бита флага С можно не заботиться, он гарантированно будет в нем установлен в 0 автоматически.
Если эти команды применялись, то нужно проконтролировать состояние бита флага С на момент начала процедуры циклического сдвига, и, если в нем установлена 1, то, в целях комфортного отслеживания работы программы, сбросить бит флага С в 0 командой bcf Status,0(C).
Для того чтобы понять, как происходит процедура циклического сдвига, достаточно провести аналогию с работой реверсивного сдвигового регистра с предустановкой типа ....ИР.. .
Хотя микросхем 9-разрядных регистров ИР и нет, но, предположим, что такой регистр существует.
Сейчас я Вам "обрисую" аналог процедуры циклического сдвига.
"Закольцовываем" регистр, соединяя выход 9-го разяда с счетным входом 1-го разряда.
8-битное исполнительное устройство, назовем его А, подключаем к выходам с 1-го по 8-й, а 1-битное исполнительное устройство, назовем его В, подключаем к 9-му выходу регистра.
Сначала, на 8-ми параллельных входах предустановки (начало нумерации - от младшего разряда) выставляются уровни, соответствующие числу, которое нужно преобразовать в результате сдвига (сдвигов), на 9-м входе предустановки выставляем 0 и записываем все это в регистр.
Это соответствует записи в регистр числа, которое нужно преобразовать в результате исполнения процедуры циклического сдвига через бит флага С и предварительному сбросу бита флага С.
Далее, на вход управления реверсом подается уровень, соответствующий, например, сдвигу влево, что соответствует выбору команды RLF.
На тактовый вход регистра подается один тактовый импульс. Это соответствует исполнению команды RLF.
Происходит сдвиг, предварительно записанной во все биты регистра, информации в сторону старших разрядов, причем, из-за наличия "закольцовки", бит с № 8 "занимает место" бита с № 0, а бит с № 7 "занимает место" бита с № 8 (остальные - соответственно).
На управляющие входы исполнительных устройств А и В подаются сдвинутые на такт влево уровни, которые, в комплексе, будут уже соответствовать числу, отличному от исходного.
Таких тактовых импульсов (команд RLF) можно сформировать несколько и, соответственно, на такую же величину разрядов, сдвинется влево содержимое 9-разрядного регистра.
После прохождения 9-ти сдвигов (на 10-м сдвиге), в регистре установится исходное число, следовательно, полный цикл сдвигов составляет 9 тактов (команд RLF).
В пределах одного регистра общего назначения можно отобразить только 8 битов этого 9-битного числа (начиная с младшего), а 9-й (старший) бит (что-то типа администратора) находится как бы "за кулисами", а "зрители", сидящие в "зрительном зале", видят, чем занимается коллектив из 8-ми "актеров" с названием "бит".
А занимаются они чем-то типа танца "летка - енка", коллективно прыгая по квадратикам, замкнутым в круг, причем, после каждого прыжка происходит смена старого администратора на нового (из числа прыгающих).
Конечно, аналогия довольно-таки идиотская, но, тем не менее, отражающая общий смысл происходящего.
А теперь давайте проанализируем "механику" этих "коллективных прыжков" и какую практическую выгоду можно из этого извлечь.
"Прогоним" по полному циклу сдвигов влево, например, 8-битное число .09.
(.009) 0000 1001 C=0. (9) начальная установка.
(.018) 0001 0010 C=0. (9*2=18).
(.036) 0010 0100 C=0. (18*2=36).
(.072) 0100 1000 C=0. (36*2=72).
(.144) 1001 0000 C=0. (72*2=144).
(.032) 0010 0000 C=1. (144*2=288-256=32).
(.065) 0100 0001 C=0. (32*2+1(перенос из бита С в младший разряд байта)=65).
(.130) 1000 0010 C=0. (65*2=130).
(.004) 0000 0100 C=1. (130*2=260-256=4).
- - - - - - - - - - - - - - - -
(.009) 0000 1001 С=0. (4*2+1(перенос из бита С в младший разряд байта)=9).
...............................
Первое, что бросается в глаза, это движение единиц.
"Привязавшись" к ним (или к нулям), можно реализовать, например, устройства типа бегущих огней, бегущей строки или что-то подобное.
А нельзя ли, при помощи циклических сдвигов, реализовать что-нибудь "посолиднее"?
Очень даже можно.
После анализа результатов циклического сдвига числа .09 или других чисел (для команды RLF), можно сделать следующие выводы:
- циклический сдвиг влево на 1 такт (позицию) всегда связан с операцией умножения "предшествующего" (зафиксированного в 8-битном регистре до этого сдвига) числа на 2.
- если результат умножения на 2 "выходит на второй виток" полного цикла чисел (более .255), то флаг С поднимается (индикация наличия переноса или выхода на второй "виток" полного цикла чисел со стороны меньших значений, что одно и то же).
- если в бите С находится 1, то результат следующего циклического сдвига (умножения на 2) увеличивается на 1.
Последовательно исполняя несколько команд RLF, исходное число можно умножить на 2, 4, 8 и т.д., но с одним "но", которое состоит в том, что, кроме подсчетов количества переносов, придется принимать меры по "нейтрализации" увеличения результатов циклических сдвигов на 1 после того, как бит флага С установится в 1.
Можно сделать и так, но, зачем "ломать голову", используя этот достаточно "неудобоваримый" алгоритм умножения, когда можно поступить гораздо проще и, в конечном итоге, эффективнее: последовательно применить несколько процедур (групп команд) умножения на 2.
Например, для умножения числа на 16, их нужно 4.
Естественно, что после исполнения каждой команды RLF нужно анализировать состояние бита С с целю ответа на вопрос: "Произошел "выход" на второй "виток" полного цикла счета или нет (перенос есть или нет)"?
Например, после умножения на 2 чисел до 127 включительно, в регистре общего назначения, к которому обращается команда RLF, будет "находиться" число, точно отображающее результат выполнения арифметической операции умножения на 2, а бит флага С всегда будет нулевым, так как, в этом случае, старший бит байта всегда равен нулю.
После умножения на 2 чисел от 128 до 255 включительно, в регистре общего назначения, к которому обращается команда RLF, будет "находиться" число, отображающее результат выполнения арифметической операции умножения на 2 с учетом "выхода" на второй "виток" полного цикла счета, а бит флага С установится в 1 (перенос).
В этом случае, для получения истинного результата умножения на 2, необходимо к числу, "находящемуся" (после исполнения команды RLF) в регистре общего назначения, к которому обращается команда RLF, прибавить число 256.
Далее можно предположить: не будет же программист, по ходу исполнения программы, "вручную" вносить поправки в результаты вычислений, нужно сделать так, чтобы этим "занималась" программа.
Как это сделать?
А очень просто: нарастить разрядность, задействовав дополнительный "обнуленный" регистр общего назначения.
О том, как осуществляется перенос из младшего байта такого двухбайтного регистра в старший байт, будет рассказано ниже.
После исполнения группы команд умножения, в таком двухбайтном регистре и "оседает" двухбайтное двоичное число, точно отображающее результат умножения.
Для того чтобы при расчетах преобразовать это двухбайтное число в десятичное, нужно, сначала, перевести содержимое обеих регистров (каждого по-отдельности) в десятичные числа (например, при помощи имеющейся у Вас таблицы), умножить десятичное число, "осевшее" в старшем байте (в котором считается количество переполнений) на 256 и сложить этот результат с десятичным числом, "осевшим" в младшем байте (в котором производились циклические сдвиги влево).
Нетрудно подсчитать, что максимальное значение десятичного числа, которое можно отобразить в таком двухбайтном регистре, не может превысить числа 256*256=65536-1=65535.
Если необходимо отобразить большие значения чисел, то следует еще на один регистр нарастить разрядность, и организовать еще один перенос в этот дополнительный регистр.
В этом случае, можно будет отобразить десятичные числа результатов операций до 256*256*256=16777216-1=16777215.
Еще раз напоминаю Вам, что все десятичные числа, с которыми оперирует программист, это, хотя и объективный, и удобный для него, но, все-таки, "плод его буйной фантазии".
ПИК таких "фантазий" "не понимает", ему подавай что-нибудь двоичное.
Чтобы двоичные числа преобрели "осязаемую" форму (например, форму показаний линейки 7-сегментных индикаторов), нужно "протащить" двоичное число через подпрограммы двоично - десятичных преобразований, речь о которых пойдет позднее.
Примечание: Вы должны заметить, что, по ходу написания "Самоучителя...", я многократно (и сознательно) обращаю Ваше внимание на некоторые "фундаментальные положения".
Если у Вас такая назойливость вызывает недовольство (типа "заладил одно и то же как пономарь"), то значит процесс обучения проходит эффективно, но не забывайте, что кроме Вас есть еще и другие люди, которые в этих положениях еще не совсем уверенно ориентируются.
Операция умножения
 Из сказанного выше становится понятным, что двоичное число без проблем можно умножить на число 2 в степени X, где X - число групп команд, посредством которых осуществляется операция умножения на 2.
Например, 2 группы таких команд производят умножение на 4, 3 группы - на 8, 4 группы - на 16, 5 групп - на 32 и т.д.
А как, например, умножить на 7 или на 10, или на любое другое число?
Арифметика здесь простая. Например, приемлемые варианты для 7: 7=2*2+1+1+1 или 7=2*2*2-1.
Для первого случая, это означает, что ПП умножения начинается с двух групп команд умножения на 2.
В 1-й группе команд множимое умножается на 2. Далее, результат этого умножения еще раз умножается на 2 во 2-й группе команд. В итоге, множимое умножается на 4.
Далее, к этому результату (умножения на 4) 3 раза последовательно прибавляется все то же множимое.
Можно сказать и так: используются 2 группы команд умножения на 2, после чего производится еще 3 суммирования множимого.
Для случая 7=2*2*2-1: используются 3 группы команд умножения на 2, после чего производится еще 1 вычитание множимого.
Какой вариант выбрать? Лучше выбрать 7=2*2*2-1, а не 7=2*2+1+1+1, так как, в этом случае, ПП умножения будет содержать меньшее количество команд.
При умножении на 10 могут быть такие варианты: 2*2*2+1+1, 2*2*2*2-1-1-1-1-1-1, (2*2+1)+(2*2+1), 2*2+1+1+1+1+1+1 и т.д.
Очевидно, что самым оптимальным, в этом случае, является 1-й вариант (для его реализации требуется минимальное количество команд).
Вот его-то, в качестве примера, давайте рассмотрим и наконец-то выясним, что из себя представляет группа команд умножения на 2 и вообще, какова конструкция ПП умножения.

Файл Rlf.asm (формализованный вид) Вы можете скачать: здесь.

Файл Rlf_1.asm (неформализованный вид) Вы можете скачать: здесь.

Я, конечно же, мог бы сразу объяснить, как работает эта подпрограмма и закрыть на этом тему, но, в обучающих целях, на примере этой подпрограммы, с моей точки зрения, имеет смысл показать "кухню" составления подпрограмм как таковых, как говорится, с "нуля".
В свое время, когда я разбирался с умножением, мне нужно было выбрать: или найти готовую ПП умножения, или составить ее самому.
Я выбрал последнее. Так "родилось" то, что Вы видите.
И дело даже не в том, что наиболее вероятным результатом такой работы является "изобретение колеса" (по всей видимости, так оно и есть), а в том, что после нее наступает сильнейшее "просветление в мозгах", и преобретается ценнейший опыт.
В конечном итоге, такой "въедливый" и достаточно трудоемкий (на первых порах) стиль "разборок с непонятностями", оказывается гораздо эффективнее, чем достаточно "мутный" и ограниченный "блочный" стиль работы, при котором программист постоянно вынужден искать что-нибудь готовенькое, особо не вдаваясь в "кухню" того, что он находит и, по этой причине, часто попадая в затруднительные ситуации.
Критерием успешности, в этом случае, является что-то типа этого: "Зачем искать готовую, нужную подпрограмму? Проще и быстрее составить ее самому."
Итак, предположим, что программисту нужно составить подпрограмму умножения, а он, кроме знания формальной стороны команд ассемблера и того, что, в этом случае, должен применяться циклический сдвиг и задействоваться флаг С, полный "ноль" во всех вопросах, касающихся "тонкостей" этой процедуры.
Что касается конкретной подпрограммы (умножения), то наиболее трудоемкий участок такого рода "штурма" нами уже пройден ("разборки" с флагом С, суммированием и циклическим сдвигом - см. выше).
Принцип умножения сформулирован. Теперь можно перейти и к "кульминационному моменту", то есть, к тому, ради чего нужно было "поднапрячься".
Конструируем подпрограмму умножения однобайтного двоичного числа на 10.
Определяемся с регистрами общего назначения.
Так как множимое является числом, отображаемым одним байтом (.00 ... .255), то такое множимое может быть записано в один регистр общего назначения.
Так как множимое может принять любое значение из диапазона чисел от .00 до .255, то, в результате циклических сдвигов, может происходить событие переноса.
Кроме того, максимальное значение произведения не может превысить числа 255*10=2550.
По совокупности этих двух утверждений, для отображения произведения, одного регистра общего назначения мало, и необходимо назначить еще один регистр общего назначения.
И в самом деле, двухбайтный регистр способен отобразить число от 0 до 256*256=65536-1=65535 и число 2550 попадает в этот числовой интервал.
Так как регистров общего назначения стало 2, то нужно определиться с порядком старшинства:
под младший байт произведения (он же - байт множимого) выделяем регистр общего назначения с названием, например, RegL.
В этом байте будет осуществляться циклический сдвиг влево, и он же будет младшим байтом произведения.
Под старший байт произведения выделяем регистр общего назначения с названием, например, RegH.
В этот байт, в ходе последовательных процедур умножения на 2, будут осуществляться переносы, которые будут, также последовательно, увеличивать значение числа, записанного в нем.
Теперь нужно определиться с начальными условиями.
Для этого нужно ответить на вопрос: "Какие числа должны быть записаны в регистры RegL, RegH и в бит флага С на момент начала исполнения первого циклического сдвига?"
Ответ: в регистр RegL будет записываться число множимого.
Байт регистра RegH нужно сбросить в 0 (clrf RegH), в противном случае имел бы место факт наличия переносов до исполнения первой команды цикличесого сдвига, что неприемлемо (увеличенное, по сравнению с истинным, значение произведения).
Бит флага С нужно сбросить в 0 (bcf Status,C), в противном случае, к результату первого циклического сдвига приплюсуется 1 (почему? - об этом говорилось ранее), а это есть погрешность умножения.
Посмотрите в текст "программы".
В нем Вы увидите обе этих команды в "подготовительных операциях".
Там же (в "шапке") Вы увидите, что регистры RegL, RegH, а также и регистр Status введены в работу.
Исходя из сформулированных выше условий, ПП умножения должна содержать не только группы команд умножения на 2, но и группы команд суммирования, причем, одним из слагаемых обязательно должно быть множимое, и операции сложения должны исполняться после завершения операции умножения на 8 (3-х операций умножения на 2).
Следовательно, в "подготовительных операциях" нужно сохранить множимое в каком-то регистре.
Можно под это дело назначить еще один (третий) регистр общего назначения, а можно и скопировать множимое в регистр W, так как он, при исполнении процедур умножения на 2, не задействуется.
Так что нечего увеличивать количество задействованных регистров общего назначения: можно обойтись двумя регистрами общего назначения и "штатным" аккумулятором (W).
Следовательно, в "подготовительных операциях" нужно сохранить множимое в регистре W командой movf RegL,W (с целью дальнейшего использования множимого в группах команд суммирования).
Итак, группа команд "подготовительных операций" сформирована.
Порядок их расположения в этой группе не имеет значения, можно "перетасовать их как карты", на результате операции умножения на 10 это не отразится, так как все 3 команды "развязаны" относительно друг друга.
Теперь можно заняться "конструированием" группы команд умножения на 2.
Возникает вопрос: "Какие именно команды должны входить в группу команд умножения на 2 и в каком порядке, в пределах этой группы, они должны располагаться ?"
Исходя из ранее изложенной информации, на данный момент, вполне определенным является то, что первой должна следовать команда циклического сдвига влево rlf RegL,F, а второй - команда опроса бита флага С: btfsc Status,C, а также то, что перенос, в случае его наличия, должен осуществляться в младший разряд старшего байта (в регистр RegH).
Что дальше? Каким образом должно происходить взаимодействие младшего и старшего байтов при переносах?
На момент начала первого циклического сдвига влево, в регистре RegH установлен 0 и, в дальнейшем, от переноса к переносу, значения чисел, "находящихся" в регистре RegH, должны только увеличиваться.
Вопрос: "По какому закону?"
Могут быть следующие варианты: суммирование, инкремент, циклический сдвиг влево.
Суммирование отпадает, так как, в этом случае, задача, в конечном итоге, сводится к "привязке" к нескольким значениям констант, практически реализовать которую ("привязку") очень затруднительно.
Инкремент приемлем для операции суммирования, и его можно применить для организации "одноступенчатой" процедуры умножения на 2 и не более того, так как, при применении от двух и более последовательных процедур умножения на 2, общий результат умножения, при наличии переносов, будет отличаться от истинного.
Чтобы прояснить этот "туман", давайте немного "погоняем" нули с единицами на бумаге.
Умножим, например, число .194 (1100 0010) на 8 (2*2*2), применив для переносов в старший байт команды инкремента.
194 00000000 11000010 ; множимое, в бите С - ноль.
388 00000001 10000100 ; в бит С "пришла" 1 из старшего бита младшего байта, следовательно, инкремент старшего байта.
521 00000010 00001001 ; из бита С "ушла" 1 в младший бит младшего байта, в бит С "пришла" 1 из старшего бита младшего байта, следовательно, инкремент старшего байта.
531 00000010 00010011 ; из бита С "ушла" 1 в младший бит младшего байта, в бит С "пришел" 0 из старшего бита младшего байта, следовательно, инкремента старшего байта нет.
В идеале, должен получиться результат 194*8=1552, а получилось 531.
Вывод: при организации умножения посредством последовательных, многоступенчатых процедур умножения на 2, операции инкремента, для организации переносов в старший байт, применять нельзя.
Проанализировав результат (нужно 1552, а получилось 531), можно прийти к выводу, что, при формировании нескольких переносов, наращивание значений чисел, формируемых в регистре RegH, должно происходить значительно большими "темпами", чем в случае применения операции инкремента.
Исходя из этого, на "роль палочки - выручалочки претендует" все та же операция циклического сдвига влево, только применяемая по отношению к содержимому старшего байта.
И в самом деле, она эквивалентна операции умножения на 2, что обеспечивает более высокие "темпы прироста", чем при применении операции инкремента.
Таким образом и "родилась" группа команд умножения на 2, которые можно последовательно соединять между собой "цепочкой".
Посмотрите в текст "программы". В данном случае, Вы видите "цепочку" из трех групп команд умножения на 2 с общим коэффициентом умножения 8.
Может возникнуть вопрос: "Как два байта "делят" между собой один бит флага С, ведь они оба работают с ним?"
А так и работают - по очереди.
Чтобы это было наглядно, давайте умножим на 8 все то же число .194 (1100 0010), только с использованием для переносов в старший байт не команды INCF, а команды RLF.
194 00000000 11000010 ; множимое, в бите С - ноль.
388 00000001 10000100 ; сдвиг в младшем байте: 0 из бита С "уходит" в младший бит младшего байта, сдвигая все биты влево, 1 старшего бита младшего байта "приходит" в бит С.
сдвиг в старшем байте: 1 из бита С "уходит" в младший бит старшего байта, сдвигая все биты влево, 0 старшего бита старшего байта "приходит" в бит С.
776 00000011 00001000 ; сдвиг в младшем байте: 0 из бита С "уходит" в младший бит младшего байта, сдвигая все биты влево, 1 старшего бита младшего байта "приходит" в бит С.
сдвиг в старшем байте: 1 из бита С "уходит" в младший бит старшего байта, сдвигая все биты влево, 0 старшего бита старшего байта "приходит" в бит С.
1552 00000110 00010000 ; сдвиг в младшем байте: 0 из бита С "уходит" в младший бит младшего байта, сдвигая все биты влево, 0 старшего бита младшего байта "приходит" в бит С.
сдвиг в старшем байте: 0 из бита С "уходит" в младший бит старшего байта, сдвигая все биты влево, 0 старшего бита старшего байта "приходит" в бит С.
Вывод: получен верный результат умножения числа 194 на 8, равный 1552.
Если анализировать изменения, происходящие с 16-битным числом (2 байта в комплексе), то, в его пределах, происходят все те же циклические сдвиги влево, что свидетельствует о том, что, посредством "цепочки" групп команд умножения на 2 с использованием команд RLF, удалось "привязать" младший байт к старшему таким образом, что конечный результат умножения в точности будет равен 2 в степени N (где N - число групп команд умножения на 2).
Проще говоря, если представить себе некий "барьер" между старшим битом младшего байта и младшим битом старшего байта, препятствующий циклическому сдвигу влево содержимого 16-битного регистра, то, при применении указанной выше "цепочки" групп команд умножения на 2, этот "барьер" разрушается.
Это приводит к тому, что два 8-битных регистра объединяются в один 16-битный регистр, и операция циклического сдвига влево происходит уже в "расширенных пределах" 16-ти битов (2-х байтов).
Вот вам и немудреная суть работы всей "цепочки" (это относится и к "цепочке" групп команд, и к "цепочке" рассуждений).
Для того чтобы закрыть тему умножения на 2 в степени N, остается только выяснить, как происходит взаимодействие групп команд умножения на 2 между собой.
Посмотрите в текст "программы".
При опросе бита флага С применяется бит-ориентированная команда ветвления btfsc.
Если флаг С поднят, то рабочая точка программы переходит на команду rlf RegH,F, а после ее исполнения - на первую команду следующей группы команд умножения на 2 (rlf RegL,F).
Это - первый сценарий.
Если флаг С опущен, то рабочая точка программы "перескакивает" команду rlf RegH,F (вспоминайте про "виртуальный" NOP) и устанавливается на первой команде следующей группы команд умножения на 2 (rlf RegL,F).
Это - второй сценарий.
Разница между этими двумя сценариями очень простая: в первом сценарии команда rlf RegH,F исполняется, а во втором - нет (вместо нее исполняется "виртуальный NOP).
В зависимости от величины множителя и выбранного способа его разложения, количество групп команд умножения на 2 может быть различным.
Например, если требуется 2 группы команд умножения на 2, то из текста "программы" нужно убрать одну группу команд умножения на 2, а если требуется 4, 5 или более, то в текст "программы нужно добавить одну, две или более группы команд умножения на 2 соответственно.
После "отработки" команд умножения на 8, рабочая точка программы устанавливается на первую команду первой группы команд суммирования результата умножения на 8 и множимого.
Вот здесь-то содержимое регистра W (множимое) и становится востребованным.
Во время исполнения групп команд умножения на 2, регистр W не задействовался и поэтому, на момент начала исполнения групп команд суммирования, в нем находится записанное в него ранее значение множимого (в данном случае - .194).
Работу этих групп команд я объяснял ранее, так что повторяться не буду.
Сразу покажу, что происходит:
В аккумуляторе (W) предварительно записано множимое .194 (1100 0010).
1552 00000110 00010000 ; результат умножения на 8.
1746 00000110 11010010 ; результат 1-го суммирования. В младшем байте - число 16+194=210. Инкремента содержимого старшего байта нет.
1940 00000111 10010100 ; результат 2-го суммирования. В младшем байте - число 210+194=404-256=148. Инкремент содержимого старшего байта (увеличение числа .06 на 1).
Итог: 194*8+194+194=194*10=1940, что и требовалось получить.
Можете использовать вместо 194-х любое другое значение множимого из числового диапазона одного байта (0...255) и Вы получите произведение, ровно в 10 раз большее, чем множимое.
Можно ли реализовать ПП умножения без использования циклических сдвигов влево, а только на основе последовательных суммирований?
Конечно же можно, но, в этом случае, объем подпрограммы увеличится (а при больших множителях - очень значительно), так что практический смысл в этом небольшой.
Возникает вопрос: "Каков может быть диапазон используемых множителей?"
В рассматриваемом случае (двухбайтный регистр), он такой же, как и диапазон используемых множимых (.00 ... .255), с учетом того, что редко когда возникает практическая необходимость умножать на 0 или 1.
Объясняется это просто: 255*255=65025 меньше, чем 256*256-1=65535, следовательно, двухбайтный регистр не переполнится (выхода на второй "виток" полного цикла чисел нет), и количество групп команд умножения на 2 не превысит 8-ми (количества разрядов старшего байта).
Если нужно умножить на 10, например, число 10000 (результат умножения больше чем 65535), то необходимо ввести в работу еще один (третий) регистр, причем число 10000 предварительно будет записываться в 2 регистра (L и M), а байт регистра H и биты № 6 и 7 регистра M предварительно нужно сбросить в 0.
В этом случае, в связи с необходимостью обслуживания работы дополнительного регистра, количество команд в группе команд умножения на 2 увеличивается.
Можно ли таким образом реализовать на ПИКе что-то типа перемножающего калькулятора?
Можно, но, в связи с относительно высокой степенью сложности такого рода подпрограмм, это не есть предмет обсуждения в "рамках" данного "Самоучителя...".
Из сказанного выше можно также сделать следующий практический вывод: если есть готовая подпрограмма, то ее можно "выпотрошить" в симуляторе, но если речь идет о создании подпрограммы, то, в ходе ее составления, особо надеяться на помощь симулятора не стоит.
Такого рода работа делается, в основном, "в мозгах и на бумаге", и только после того, как "родится" подпрограмма или какая-то функционально завершенная ее часть, можно (и нужно) задействовать симулятор для оценки ее "жизнеспособности".
Вывод: "на симулятор надейся, а сам не плошай".
То же самое можно сказать и о "бесплатном сыре" в виде "готовых к употреблению" подпрограмм.
На практике часто возникает потребность в их "модернизациях" под свои задумки, и если, перед этим, как следует не "погонять" нули с единицами в стандартных (классических) подпрограммах, в первую очередь, в голове и на бумаге, а затем и в симуляторе, то можно, в точном соответствии с "классическим" утверждением, банально "попасть в мышеловку" своей некомпетентности.
В принципе, программирование это такая же работа, как и любая другая, только связанная с большим массивом, изначально "сваленной" в большую и привлекательную "кучу", информации.
Кто сумеет "расчленить" этот массив на функционально законченные части, разобраться с каждой из них и "разложить их по полочкам" так, чтобы они не "наезжали" друг на друга и чтобы было понятно "что где лежит", тот в этой "битве" и будет победителем.
Что касается программного "прожиточного минимума" (стандартные подпрограммы), с которыми я Вам предлагаю познакомиться, то аналогия с бесплатным сыром и мышеловкой здесь вряд ли уместна, так как речь идет о базовых основах программирования, на "фундаменте" которых "строится" все остальное.
Так что, хочешь - не хочешь, а "попотеть" над ними, зарабатывая этот самый "сыр", придется.
Переходите к следующему разделу.
Дополнительно
 Имеется хорошая программка BinHexDec.exe. Это как раз то, что нужно при "разборках" с сдвигами.
При помощи этой программки, Вы сможете понять, как, в пределах двух байтов, происходят сдвиги вправо и влево (правда, без переносов через бит С) и какие изменения при этом происходят с числами.
Она проста и Вы без труда разберетесь, как она работает.
Ее предоставил мне Иван Васильевич (к сожалению, не знаю фамилии).
Файл BinHexDec.exe (631 кб) Вы можете скачать на моем сайте в 14-м разделе "Самоучителя...".


Kea_35

15. Введение в принцип построения подпрограммы динамической индикации. Косвенная адресация.

А теперь, имея некоторый опыт и навыки, давайте разберемся, как на практике реализовать такое архинужное устройство, как устройство динамической индикации.
Совершенно понятно, что, само по себе, это устройство особой практической ценности не представляет так же как, например, отдельная микросхема (что отображать? Отображать нечего).
Устройство динамической индикации должно входить в состав более сложного устройства "на правах" его функционально обособленного и очень "ответственного" блока, который обрабатывает и отображает (выводит на индикацию) конечный результат "деятельности" всего устройства в удобном, для визуального восприятия человеком, виде.
При этом могут отображаться не только цифры, но и другие символы, которые сможет отобразить применяемый индикатор.
Почему, когда речь идет о микропроцессорной технике, выбирается именно динамический способ индикации, а, например, не статический способ индикации, применяемый "на заре" цифровой техники (вспомните газоразрядные десятичные индикаторы с "кучей" проводов подключенных к ним и "кучей" микросхем, обслуживающих их работу)?
Объяснение простое: на эту "старушку" не хватит портов даже у самого "крутого" микроконтроллера, а если и хватит, то "овчинка выделки не стоит", ведь можно получить то же самый результат более простым и дешевым способом, перейдя на динамическую индикацию.
В основном требуется отобразить цифры, и поэтому, в большинстве случаев, речь заходит о 7-сегментных индикаторах на основе светодиодов или жидких кристаллов.
Большая часть микроконтроллеров имеют 8-выводные порты, которые (в пределах порта) "при всем желании" не смогут работать с индикаторами, имеющими более 8-ми активных выводов.
Конечно же, в этом случае, можно дополнительно задействовать выводы другого порта, но оставшихся выводов может просто не хватить для реализации других функций.
Особенно это актуально для "слабосильных" микроконтроллеров типа PIC16F84A, PIC16F628A и т.д., которые имеют всего 2 порта: один - "полноценный", 8-битный (В), а второй - "неполноценный", 5-битный (А).
В этом случае, порт В, "в полном своем составе", выделяется для "обслуживания" линейки 7-сегментных индикаторов, а функции "общения" с внешними устройствами передаются в порт А (см. схему ЧМ/ЦШ А.Денисова).
"Привяжусь" именно к этой принципиальной схеме, и, для разнообразия, рассмотрю случай организации динамической индикации не для семи, а для 8-разрядной линейки 7-сегментных индикаторов.
Примечание: то, что будет рассматриваться - вовсе не частный случай. Группа подпрограмм динамической индикации (программа, подпрограмма, группы команд - можно назвать как угодно, я буду называть ее группой подпрограмм динамической индикации) имеет универсальный характер и может быть "встроена" в другие программы различного функционального предназначения.
Разрядность можно будет уменьшить (просто) или увеличить (сложнее), о соответствующих коррекциях я расскажу.
Настоятельно советую Вам как следует разобраться с тем, что Вы прочитаете.
Лично я, так и не встретил хотя бы более-менее внятного объяснения работы этой группы подпрограмм, не говоря уж о какой-то универсальной "заготовке", расчитанной на начинающих и не только (если я ошибаюсь, то поправьте).
"Потрошить" группу подпрограмм динамической индикации будем в 2 этапа: образно выражаясь, на 1-м этапе сформируем "скелет", а на 2-м - нарастим на нем "мясо".
По идее, при "разборках" с такого рода группами подпрограмм, нужно "отталкиваться" от блок - схемы группы подпрограмм.
Я поступил так: совместил текст группы подпрограмм с ее блок - схемой, что позволяет, не "распыляя" внимания, отслеживать и то, и другое в одном месте (в тексте группы подпрограмм).
По ходу дела разберемся с косвенной адресацией, и я расскажу о нестандартном, "хитром" способе записи константы в регистр, в реализации которого, косвенно, принимает участие директива equ (думаю, что этот "прибамбас" попортил нервы многим программистам).

Первый этап

 Предмет разбирательства представляет собой текст некой "программы" с названием Fsr.asm, в которую "встроена" группа подпрограмм динамической индикации, обслуживающих линейку из 8-ми 7-сегментных светодиодных индикаторов с общим катодом, предназначенную для работы в устройствах динамической индикации.
Это означает, что, внутри линейки, аноды светодиодов одноименных секторов запараллелены (8 выводов, подключаемых через гасящие резисторы к выводам порта В - см. схему ЧМ/ЦШ А.Денисова), и, внутри каждого 7-сегментного индикатора, выводы катодов каждого из 8-ми светодиодов также запараллелены (8 выводов, подключаемых к выходам дешифратора 555ИД7).

Файл "программы" Fsr.asm Вы можете скачать: здесь.

Отталкиваемся от общего принципа динамической индикации, суть которого заключается в последовательной активации 7-сегментных индикаторов.
Активация индикатора это, проще говоря, его "загорание".
Практически всегда полный цикл активации начинается с младшего по разрядности 7-сегментного индикатора (правого в линейке), затем, "погорев" определенное время, он "гасится" и тут же активируется следующий индикатор более старшего разряда и т.д. до тех пор, пока не "погаснет" индикатор самого старшего разряда.
В момент его "погасания" (перехода в пассивное состояние), текущий полный цикл активации заканчивается и тут же начинается следующий полный цикл активации (активируется самый младший разряд) и т.д.
Вот Вам и типичная "закольцовка", которую и нужно реализовать в группе подпрограмм динамической индикации.
Назовем это кольцо большим кольцом индикации..
В нашем случае, время большого кольца индикации равно суммарному времени активации всех 8-ми разрядов линейки.
После активации, каждый из индикаторов должен какое-то время "погореть" (какое-то время находиться в активном состоянии), что вполне понятно, иначе мы просто ничего не увидим.
Таким образом, речь идет о задержке, суть которой - все та же "закольцовка".
Вот Вам и еще одна "закольцовка", которую нужно реализовать в группе подпрограмм динамической индикации.
Назовем это кольцо малым кольцом индикации.
Естественно, при любом раскладе, время прохождения (рабочей точкой программы) малого кольца индикации будет в 8 раз меньше времени прохождения большого кольца индикации, и последнее будет "привязано" к времени прохождения малого кольца индикации.
Зависимость простая: время прохождения одного большого кольца индикации равно суммарному времени прохождения 8-ми малых колец индикации.
Таким образом, время прохождения малого кольца индикации является как бы "базовым" и от него зависит скорость последовательной активации разрядов линейки и размер (во времени) большого кольца индикации.
Если эту скорость сделать маленькой (меньшей чем 24 "кадра"/сек. деленное на количество индикаторов в линейке, то есть, 8 или 41,666... : 8 = 5,2083332 мсек.), то визуально можно будет наблюдать процесс так называемых "мерцаний" (визуально ощущаемых активаций).
Естественно, это не вполне удобно и поэтому, на практике, скорость последовательной активации разрядов линейки стремятся как можно более увеличить с тем расчетом, чтобы и устранить негативные последствия "мерцаний", и существенно не снизить яркость свечения разрядов линейки (при очень высокой скорости следует учитывать инерционность светодиодов).
Далее возникает вопрос о "рулевом", ведь что-то должно "рулить" процессом последовательной активации.
И в самом деле, должно же быть "что-то", что давало бы команду на начало активации следующего разряда линейки после того, как активация предыдущего разряда линейки закончится, причем, исходя из элементарной логики, это "что-то" должно быть встроено в малое кольцо индикации и иметь "привязку" к количеству индикаторов линейки 7-сегментных индикаторов.
По окончании активации текущего разряда линейки, содержимое этого "чего-то" должно изменяться на 1 таким образом, чтобы на следующем малом кольце индикации произошла активация следующего по старшинству индикатора линейки.
Забегая вперед, скажу, что роль этого "чего-то" исполняет регистр общего назначения с названием Index, который работает как счетчик количества малых колец индикации.
Он встроен в малое кольцо индикации и инкрементируется в конце интервала времени активации с последующим анализом его содержимого.
В пределах чисел от .00 до .07 рабочая точка программы последовательно "наматывает" 8 малых колец индикации, а в начале 9-го кольца (число .08) текущий полный цикл большого кольца индикации заканчивается и тут же начинается формирование нового большого кольца индикации, в начале которого число .08, находящееся в регистре Index, сбрасывается в 0.
Ну хорошо, предположим, что процесс динамической индикации организован, но ведь это же - не самоцель. А отображать-то что?
Если рабочая точка программы все время, пока устройство включено, "мотает круги" в группе подпрограмм динамической индикации, не выходя из них, то такое устройство и даром не нужно.
Это как в "12-ти стульях": "Все это великолепие разбивалось о вывеску "Штанов нет"".
Вывод: рабочая точка программы, сделав "все дела" в группе подпрограмм индикации, должна из нее выйти для того, чтобы каким-то образом (вариантов может быть много), в остальных подпрограммах и группах команд программы, функционально не относящихся к группе подпрограмм динамической индикации, сформировать число, которое нужно отобразить в линейке.
Между выходом рабочей точки программы на новый полный цикл программы и к моменту ее очередного "захода" в группу подпрограмм динамической индикации, программа должна окончательно сформировать число, которое нужно вывести на индикацию с тем, чтобы было что на эту самую индикацию выводить.
Далее следует простой вывод: необходимо ограничить "пребывание" рабочей точки программы в группе подпрограмм динамической индикации.
Каким образом?
Ответ напрашивается сам собой - "поставить на счетчик". Покрутилась (речь идет о рабочей точке программы) в группе подпрограмм динамической индикации определенное количество больших циклов индикации - будь добра покинь ее и подготовь то, что нужно отобразить на следующем полном цикле программы. Вот тогда и "милости просим".
Звучит достаточно по-идиотски, но, тем не менее, советую Вам представить себе рабочую точку программы как некое родное и своенравное живое существо: чем-то типа непокорного блудного сына, которого постоянно нужно учить уму-разуму и опекать, чтобы он не сотворил чего-то нехорошего.
Парадоксально, но срабатывает на все сто, на себе проверил.
Итак, для того, чтобы ограничить время пребывания рабочей точки программы в группе подпрограмм динамической индикации, нужно назначить, в качестве счетчика количества больших колец индикации, регистр общего назначения, предварительно заложить в него константу (заданное количество больших колец индикации), "вставить" его в большое кольцо индикации (лучше - в конце) и декрементировать его содержимое на каждом большом кольце индикации.
После того, как в счетчике будет 0, нужно, при помощи команды ветвления, перейти к сценарию "программа исполняется далее", выйдя таким образом из группы подпрограмм динамической индикации.
Подобные "манипуляции" уже описывались ранее, так что ничего нового в этом нет.
Итак, мы имеем:
- малое кольцо индикации,
- большое кольцо индикации,
- полный цикл динамической индикации,
- полное кольцо программы.
Почему "полный цикл динамической индикации", а не "полное кольцо динамической индикации"?
Потому что полный цикл динамической индикации не является кольцом (непосредственного перехода с конца группы ПП динамической индикации в ее начало нет, этот переход осуществляется через полное кольцо программы).
А теперь, начиная с малого кольца индикации, представьте себе, как происходит движение рабочей точки программы и какие "фигуры высшего пилотажа" при этом она проделывает.
Если при этом будут затруднения, то еще раз повнимательнее прочитайте то, о чем говорилось выше.
Прежде чем перейти к деталям, осталось выяснить, каким же должно быть соотношение времени прохождения полного кольца программы к времени исполнения группы подпрограмм динамической индикации.
В идеале, это соотношение должно быть чуть меньше 1 при как можно меньшем интервале времени между выходом рабочей точки программы из группы подпрограмм динамической индикации и началом следующего полного цикла динамической индикации, то есть, рабочая точка программы должна основную часть времени прохождения полного кольца программы "крутиться" в группе подпрограмм динамической индикации, а указанный выше "участок" "проскакивать" очень быстро.
Дело в том, что после отработки группы подпрограмм динамической индикации и до начала нового полного цикла динамической индикации (процесс динамической индикации прерван), в линейке будет постоянно высвечиваться один из его разрядов, в большинстве случаев - младший, а остальные будут погашены.
В этом случае, после выхода рабочей точки программы из группы подпрограмм динамической индикации, нужно сбросить байт регистра PortB в ноль командой clrf PortB, после этого активные секторы высвечиваемого разряда линейки погаснут.
Так или иначе, но интервалы времени, в течение которых рабочая точка программы будет находиться вне группы подпрограмм динамической индикации, будут визуально восприниматься как периодические "моргания" (еще одна разновидность "мерцаний") с периодом равным полному циклу программы.
Естественно, для того чтобы максимально ослабить такой негативный визуальный эффект, необходимо максимально уменьшить время этого "моргания", то есть, стремиться к тому, чтобы та часть полного кольца программы, которая не относится к группе подпрограмм динамической индикации, была как можно меньше, что, в идеале, предполагает отсутствие в ней подпрограмм задержек и малое общее количество команд, причем, гораздо актуальнее первое, чем второе, так как, даже при большом общем количестве команд (и при отсутствии подпрограмм задержек), время прохождения рабочей точкой программы указанного выше участка (та же задержка) будет весьма незначительным в связи с тем, что отдельные команды выполняются очень быстро (1 или 2 м.ц.).
По отношению к подпрограммам задержки этого сказать нельзя по определению, и если они имеются в наличии, то, например, задержка, формируемая даже одной такой подпрограммой, может быть больше, чем время прохождения рабочей точкой программы всех остальных команд указанного выше участка.
А если таких подпрограмм несколько? Получим "сильнейшие моргания/мерцания".
Вывод в идеале: при разработке программы, которая имеет в своем составе группу подпрограмм динамической индикации, необходимо сделать так, чтобы в состав "участка" полного кольца программы между концом и началом группы подпрограмм динамической индикации не входила ни одна ПП задержки, и он ("участок") был "линейным".
На практике, такое возможно сделать только в простых программах.
По мере возрастания их сложности, программист вынужден будет идти на компромисс.
Оптимальный вариант такого компромисса следующий:
в группу подпрограмм динамической индикации "встраиваются" (переносятся) те подпрограммы задержки и группы команд из указанного выше "участка", которые из него можно "безнаказанно изъять" (работа программы не нарушается).
Если при этом переносе требуется "корректировка", то она производится.
Эти подпрограммы и группы команд не должны заканчиваться командами ветвления, иначе рабочая точка программы может "уйти" из группы подпрограмм динамической индикации до окончания ее полного цикла, что неприемлемо.
Значения задержек тех ПП задержки, которые перенести нельзя, нужно стремиться сделать как можно меньшими (естественно, исходя из нижнего предела дозволенного).
"Линейные участки" должны иметь минимально возможное количество команд.
Возникает вопрос: "А можно ли перенести в группу подпрограмм динамической индикации указанные выше ПП задержки и группы команд, не нарушив нормальной работы группы подпрограмм динамической индикации?"
Очень даже можно и нужно.
Теория этого утверждения следующая:
выше было указано, что малое кольцо индикации является базовым, то есть, время его прохождения определяет все основные временные характеристики группы подпрограмм динамической индикации.
Величина этого времени составляет порядка нескольких мс. (с учетом указанных выше ограничений), то есть, она достаточно большая (за это время, на "линейном" участке программы, может быть исполнено несколько тысяч команд).
Для обеспечения такой большой задержки, нужно вводить в малое кольцо индикации "чистую" ПП задержки, "чистую" в том смысле, что кроме функции задержки, никаких других "полезных" функций она не выполняет.
А ведь за это время (вернее, за часть этого времени) можно сделать много других "полезных дел", о чем, фактически, и говорилось выше.
Например, изначально (до "встраивания") время задержки "чистой" ПП задержки группы ПП динамической индикации составляет 3 мс.
Программист решил "встроить" в малое кольцо индикации (т. е. в группу ПП динамической индикации) "инородные" ПП задержки и группы команд общей "протяженностью" в 2 мс.
После этого, для обеспечения исходной задержки 3 мс., необходимо уменьшить время задержки "чистой" ПП задержки группы ПП динамической индикации с 3 до 1 мс.
Все, после этого, "чистая" задержка "превращается из лентяя в работягу", который, по-совместительству, делает "кучу других полезных дел" (то, что "прикажет" программист).
Это, конечно, весьма примитивное объяснение, но, тем не менее, отражающее суть происходящего.
Реально же, такой "чистой" задержки нет, так как, по большому счету, даже отдельная команда может рассматриваться как задержка величиной в 1 или 2 м.ц..
Посмотрите в текст "программы" Fsr.asm.
Немного забегая вперед, скажу, что все команды от 1-й команды ПП CYCLE и до 1-й, по ходу текста "программы" (их всего две), команды goto CYCLE, входят в состав малого кольца индикации.
Обратите внимание, что, кроме "родной" подпрограммы задержки, в малое кольцо индикации "врезаны" и другие "родные" (относящиеся исключительно к группе ПП динамической индикации) группы команд (подпрограммы).
C таким же успехом, в малое кольцо индикации, можно "врезать" и другие, "неродные" подпрограммы или группы команд, не относящиеся к группе ПП динамической индикации (с учетом сказанного).
Это как раз то, о чем говорилось выше.
Естественно, в этом случае, нужно, на соответствующую величину, уменьшить значение времени задержки "родной" ПП задержки.
И еще одна важная особенность, связанная с группой подпрограмм динамической индикации.
Предположим, что разрабатывается измерительное устройство с отображением результатов измерения в линейке 7-сегментных индикаторов, например, частотомер.
Следовательно, нужно сформировать довольно-таки протяженный во времени измерительный интервал.
Если группа подпрограмм динамической индикации будет работать отдельно от группы программ формирования измерительного интервала времени, то соотношение времени прохождения полного кольца программы к времени полного цикла динамической индикации очень сильно ухудшится, что приведет к сильнейшим "мерцаниям".
Вопрос: " Как выйти из этого затруднительного положения?"
Ответ: Измерительный интервал времени должен формироваться во время формирования полного цикла динамической индикации. В этом случае "и волки сыты и овцы целы".
Как это делается?
А очень просто: перед началом полного цикла динамической индикации счетный вход микроконтроллера разблокируется (начало счета), а после окончания полного цикла динамической индикации - блокируется (конец счета) с таким расчетом, чтобы промежуток времени между разблокировкой и блокировкой был точно равен измерительному интервалу времени.
Из этого следует такой же простой вывод: время полного цикла динамической индикации нужно сделать несколько меньшим, чем интервал времени измерения, а затем, сразу же после окончания этого цикла и до момента блокировки счетного входа микроконтроллера (окончания счета), произвести его точную "доводку" до значения интервала времени измерения.
Естественно, в этом случае, в полный цикл динамической индикации не должны входить команды ветвления, в результате исполнения которых, рабочая точка программы могла бы "уйти" из группы подпрограмм динамической индикации до окончания полного цикла динамической индикации.
Примечание: в группу подпрограмм динамической индикации могут (и обязательно должны) входить команды ветвления, кольца сценариев исполнения которых не выходят за пределы полного цикла динамической индикации (см. текст "программы" Fsr.asm).
А теперь, после того, как имеется общее представление о принципе работы подпрограмм динамической индикации, займемся деталями.
Работаем с текстом "программы" Fsr.asm.
На "шапке" программы останавливаться не буду, в ней "прописано" все то, что используется в рабочей части "программы".
ПП START - стандартная "стартовая" ПП, с учетом специфики разрабатываемого устройства (подготовительные операции).
Далее следует группа подпрограмм преобразования двоичных чисел в двоично - десятичные.
Обоснование необходимости наличия этой группы было дано ранее, а их "разбор" будет позднее.
Перед началом "активных действий" необходимо "привязаться" к некой начальной точке отсчета, то есть, дать ответ на вопрос: "Какой разряд линейки нужно активизировать первым после "захода" рабочей точки программы в группу подпрограмм динамической индикации?"
Обычно, "привязываются" к самому младшему разряду линейки, что и делается при помощи сброса содержимого счетчика малых колец индикации в 0 (clrf Index).
Далее, перед началом малого кольца индикации, нужно записать в счетчик количества больших колец индикации Count константу, то есть, число больших колец индикации, которые должна "накрутить" рабочая точка программы за полный цикл динамической индикации (с тем, чтобы после обнуления этого счетчика, рабочая точка программы вышла из группы ПП динамической индикации по сценарию "программа исполняется далее").
Константа показана в общем виде (X). Ее величину определяет программист.
Далее, по тексту "программы" Вы видите "точку входа" в малое и большое (одновременно) кольца индикации, которой является первая команда ПП CYCLE (movlw LED0).
Посмотрите в "шапку" "программы".
Вы увидите, что в ней "прописаны" 8 регистров общего назначения LED0 ... LED7.
Эти регистры являются регистрами оперативой памяти, и в них, поразрядно, хранятся двоично - десятичные числа, которые нужно отобразить и которые предварительно в них закладываются до "захода" рабочей точки программы в группу ПП динамической индикации (в конце исполнения группы ПП преобразований двоичных чисел в двоично - десятичные).
За время полного цикла динамической индикации, их содержимое не меняется.
В тексте "программы" Вы видите только обращение к регистру LED0.
Куда из текста "программы" "испарились" остальные 7 регистров (LED1 ... LED7), ведь в нем, к их содержимому, нет обращений, но оно (содержимое) обязательно должно быть поразрядно выведено на индикацию?
Эти "чудеса" называются косвенной адресацией.
Прежде всего, к ней нужно подготовится.
Необходимость этой подготовки обусловлена тем, что, при каждом переходе с предыдущего малого кольца индикации на последующее, нужно активировать следующий по старшинству 7-сегментный индикатор (разряд) линейки, то есть, самим принципом динамической индикации.
Команда movlw LED0 является одной из двух таких подготовительных команд, причем, командой "хитрой".
По определению, с ее помощью в регистр W должна быть записана некая константа, но только где она?
На ее месте стоит LED0. Но какая же это константа?
А самая что ни на есть натуральная.
Посмотрите в "шапку" программы.
Регистру LED0 директивой equ присвоен адрес в области оперативной памяти 10h, но это же есть число, которое может быть и константой.
MPLABу "наплевать", что программист подразумевает под этим числом адрес регистра LED0 в области оперативной памяти, MPLAB "видит" только число, которое поставлено в соответствие комбинации символов LED0.
Следовательно, если, при помощи команды movlw обратиться к этой комбинации символов, то ей (комбинации) автоматически будет поставлено в соответствие число 10h, то есть, константа, которую и нужно записать в регистр W.
Таким образом, в регистр W записывается адрес регистра LED0 в области оперативной памяти, являющийся, как бы, второй начальной точкой отсчета (первая - 0 в регистре Index, см. выше), причем, точкой отсчета фиксированной (не меняющейся в пределах полного цикла динамической индикации).
В отличие от 2-й начальной точки отсчета, значение 1-й начальной точки отсчета (содержимое регистра Index равное 0) от "витка" к "витку" малого кольца индикации будет последовательно, циклически изменяться в диапазоне чисел от .00 до .08.
Следовательно, если на каждом "витке" малого кольца индикации производить суммирование фиксированной константы 10h (адрес регистра LED0) и содержимого регистра Index (см. текст "программы": команда addwf Index,W), то результат суммирования, сохраняемый в регистре W, от "витка" к "витку" малого кольца индикации будет последовательно, циклически изменяться в диапазоне чисел от 10h до 17h.
Почему не до 18h? Потому что "появление" в регистре Index восьмерки есть критерий сброса его содержимого в 0, который происходит в самом начале ее "появления".
Таким образом, в процессе работы группы ПП динамической индикации, в регистре W будет последовательно, циклически происходить перебор адресов регистров LED0 ... LED7.
Зачем это нужно, и причем тут содержимое этих регистров, ведь, опять же, речь идет только об их адресах?
Чтобы ответить на этот вопрос, нужно поподробнее разобраться с тем, что называется косвенной адресацией.
До сих пор использовалась только прямая адресация, при которой команда обращается к содержимому регистров "напрямую", без всяких "выкрутасов".
Косвенная адресация есть способ обращения к содержимому регистра через его адрес без указания названия регистра, что вызывает, особенно у начинающих, определенный "напряг".
Эта "темная лошадка" многим попортила нервы (в том числе, в свое время, и мне), так что имеет большой практический смысл "скинуть с нее завесу таинственности" и тем более, что косвенная адресация - вещь исключительно полезная, так как, очень часто, ее применение позволяет решать различные задачи с использованием малого количества команд (рассматриваемый случай как раз из этой "оперы").
Косвенная адресация начинается с копирования "откуда-то" адреса регистра, с содержимым которого нужно произвести какое-либо действие, в регистр специального назначения с названием FSR.
Он задействуется только при косвенной адресации.
Адрес этого регистра копируется в регистр FSR из регистра W командой movwf FSR.
Вывод: предварительно, то есть, до исполнения команды movwf FSR, в регистр W должен быть записан адрес того регистра, с содержимым которого предполагается произвести какое-либо действие.
Напоминаю Вам, что, в разбираемом случае, в регистре W, на первом "витке" малого кольца индикации, записано число 10h и далее, по ходу работы группы ПП динамической индикации, в регистр W будут последовательно и периодически записываться числа от 10h до 17h, то есть, адреса регистров LED0 ... LED7, которые и будут также последовательно и периодически копироваться, на каждом "витке" малого кольца индикации, из регистра W в регистр FSR, что и требуется для косвенной адресации (последовательный перебор адресов).
Вторая обязательная команда косвенной адресации это команда mowf Indf,W.
При выполнении этой команды, содержимое регистра с адресом, указанным в регистре FSR, копируется в регистр W, то есть, обращение к регистру Indf соответствует обращению к содержимому регистра с адресом, записанным в регистре FSR.
Регистр специального назначения INDF, также, как и регистр специального назначения FSR, задействуется только при косвенной адресации.
Оба они находятся в нулевом банке и отображаются на первый банк, так что работать с ними можно в обеих банках (перед началом косвенной адресации и в ее процессе можно вообще не обращать внимания, в каком банке происходит работа).
Итак, в результате работы первых 4-х команд ПП CYCLE, на каждом "витке" малого кольца индикации, в регистр W будет по очереди последовательно и периодически копироваться содержимое регистров LED0 ... LED7.
Содержимое регистров LED0 ... LED7 представляет собой 8 двоично - десятичных чисел, в диапазоне от 0 до 9, с распределенным приоритетом по разрядности, значения которых, поразрядно, нужно отобразить (после кодирования) в соответствующих разрядах линейки.
Это мы уже проходили: вспоминайте вычисляемый переход и ПП TABLE, которая перекодирует двоично - десятичный код в код 7-сегментных индикаторов ("программа" addwfpc.asm).
В регистре W находится то, что нужно перекодировать, так что, останавливаться на этом я не буду.
Только что мы рассмотрели пример организации косвенной адресации с обращением к таблице данных (к таблице перекодировки).
То же самое можно сделать и без использования косвенной адресации, но, в этом случае, количество команд возрастет, и весьма существенно.
Так что, когда необходимо работать с подобными таблицами данных, практически всегда применяется косвенная адресация.
Для "входа в таблицу", используется команда условного перехода call.
В стек закладывается адрес следующей команды (первой команды группы команд установки запятой) и возврат по стеку из ПП TABLE будет произведен именно на эту команду, после чего программа будет исполняться далее.
Выгоднее всего (для "наведения порядка" в тексте программы) ПП TABLE разместить в конце программы, непосредственно перед директивой end, как это сделано в тексте разбираемой "программы", но можно ее и "вклинить" между любыми другими подпрограммами (группами команд) программы (на "линейном" участке), просто, в этом случае, чтобы ликвидировать разрыв "линейного" участка программы, нужно будет организовывать безусловный переход на подпрограмму или метку.
Посмотрите в текст "программы" Fsr.asm.
После команды call TABLE, Вы видите "линейный" участок из 4 последовательно исполняемых групп команд.
Детально, их состав и работа будут рассмотрены на 2-м этапе "разборок", а пока, буквально пару слов о них.
Зачем нужна и что делает группа команд установки запятой, понятно уже из ее названия.
Группа команд формирования адресного кода управления дешифратором формирует этот самый адресный код.
Если рассматривать принципиальную схему ЧМ/ЦШ А.Денисова, то в ней, в качестве дешифратора, используется микросхема 555ИД7, которая имеет 3 адресных входа и 8 выходов.
В зависимости от адресного кода, на каком-либо одном из 8 выходов устанавливается 0 (на остальных 1), в результате чего активируется тот 7-сегментный индикатор линейки, к общим катодам которого подключен этот выход (тот, на котором 0).
Возникает вопрос: "Mожно ли обойтись без внешнего дешифратора, передав его функции микроконтроллеру?
Ответ: да, можно, но только в случае наличия еще одного (кроме двух задействованных) 8-выводного порта, к которому могут быть подключены 8 общих катодов 7-сегментных индикаторов линейки.
В PIC16F84A такого третьего порта нет и поэтому применение внешнего дешифратора необходимо.
Для микроконтроллеров с количеством портов не менее 3-х, дешифрацию можно организовать программными средствами, и в предлагаемую Вашему вниманию "заготовку" "программы", на место группы команд формирования адрессного кода управления дешифратором, нужно "поставить" группу команд не только формирующую адрессный код, но и группу команд, реализующую сам дешифратор.
Группа команд вывода десятичной цифры на индикацию, также в особых пояснениях не нуждается: перекодированный байт записывается в защелки порта В.
Цифра, записанная в этих защелках, будет индицироваться в том 7-сегментном индикаторе линейки, на общих катодах которого дешифратором установлен 0.
Группа команд задержки, определяющей время нахождения одного 7-сегментного индикатора в активном состоянии - название условное по той простой причине, что это время определяет не только такая задержка, но и суммарная задержка "врезанных" в малое кольцо индикации подпрограмм и групп команд.
Задержка группы команд задержки является "чистой", то есть не выполняющей каких-то активных функций, кроме задержки, как таковой.
Изменяя значение (значения) константы этой группы команд, можно "подогнать" значение времени прохождения полного цикла малого кольца индикации под какую-то величину, заданную программистом.
Итак, будем считать, что величина времени "прохождения" малого кольца индикации, в основном, сформирована и "пора переходить" на следующее малое кольцо индикации.
Для того чтобы, на этом следующем малом кольце, на индикацию были выведены данные из следующего по разрядности регистра LED и адресный код дешифратора изменился таким образом, чтобы активировался следующий по разрядности 7-сегментный индикатор линейки, необходимо увеличить на 1 содержимое "рулевого" регистра Index, что и происходит после исполнения команды incf Index,F.
Если произошло увеличение содержимого "рулевого" регистра Index на 1 (а оно произошло), то нужно обязательно проверить, а не нужно ли, после инкремента, переходить на большое кольцо индикации, то есть, не установилась ли в регистре Index восьмерка?
Для этого организуется проверочная группа команд с задействованием флага нулевого результата Z.
Это мы также уже проходили при "разборках" с флагом Z.
Отличие заключается только в том, что, вместо команды операции с константой sublw, используется команда операции с содержимым регистра subwf.
В регистр W записывается константа .08, флаг Z предварительно сбрасывается в 0, из содержимого регистра Index вычитается содержимое регистра W (то есть, число 8) и, после этого, проверяется состояние флага нулевого результата Z.
Совершенно понятно, что нулевым результат этой арифметической операции будет только в том случае, если, после инкремента, в регистре Index будет записано число .08.
Если число, записанное в регистре Index отлично от 8-ми, то, после проверки состояния флага Z, произойдет безусловный переход в ПП CYCLE(первый сценарий), то есть на новый "виток" малого кольца индикации, после чего, описанная выше "цепочка событий" повторится, но только "обрабатываться" будет следующий по старшинству разряд (байт) группы регистров LED.
Если число, записанное в "рулевом" регистре Index будет равно 8-ми, то, после проверки состояния флага Z, первая (по ходу текста "программы") команда goto CYCLE игнорируется (вместо нее исполняется "виртуальный" NOP) и рабочая точка программы продолжит "движение" по сценарию "программа исполняется далее" (второй сценарий).
В соответствии с этим сценарием, выполняется команда nop.
Этот nop - выравнивающий в том смысле, что он устраняет разницу в 1 м.ц. между временем перехода к исполнению 1-й команды 1-го сценария и временем перехода к исполнению 1-й команды 2-го сценария.
И в самом деле, переход рабочей точки программы на 1-ю команду 1-го сценария происходит через 2 м.ц. (команда goto исполняется за 2 м.ц.), а ее переход на 1-ю команду 2-го сценария - через 1 м.ц. (команда "виртуального" NOPа исполняется за 1 м.ц.).
Если речь идет только о динамической индикации, то выравнивающий nop не нужен, так как визуально что-то ощутить в течение 1 м.ц. (и даже более) просто невозможно, но если группа ПП динамической индикации участвует в формировании калиброванных интервалов времени (частотомер и т.д.), то выравнивание необходимо по той причине, что , без него, показания линейки будут нестабильными даже при абсолютно стабильном значении отображаемого параметра приведенного ко входу устройства динамической индикации, из-за нестабильности калиброванного измерительного интервала времени.
На этот "невзрачный" выравнивающий nop я обращаю Ваше внимание по той причине, что необходимость в выравнивании времени переходов рабочей точки программы на первые команды обеих сценариев, после исполнения команд ветвления, возникает очень часто, и особенно тогда, когда речь идет о формировании калиброванных и высокостабильных интервалов времени.
Такое выравнивание особенно актуально при переходах с "малых" колец на "большие".
Как раз "механику" такого перехода мы сейчас и рассматриваем.
Даже если и не требуется высокой стабильности, то, все-таки, лучше вставить выравнивающий nop, хуже от этого не будет.
В данном случае, он только один, но есть случаи, когда их может потребоваться и несколько.
Итак, в случае исполнения сценария "программа исполняется далее", после выравнивающего NOPа, исполняется команда clrf Index, после исполнения которой, байт "рулевого" регистра Index сбрасывается в 0.
Таким образом, подготавливается переход на следующее большое кольцо индикации.
Перед тем, как его осуществить, необходимо, по причине, изложенной выше, уменьшить на 1 содержимое счетчика больших колец индикации Count, с сохранением результата декремента в нем же (decfsz Count,F).
Команда ветвления decfsz, кроме указанной выше операции, еще и осуществляет проверку: "результат декремента равен или не равен нулю?"
Если результат декремента содержимого регистра Count не равен нулю, то, после исполнения команды безусловного перехода goto CYCLE, рабочая точка программы "уйдет" на точку входа в следующее большое кольцо индикации.
В дальнейшем, после последовательного вывода на индикацию всех 8-ми десятичных чисел, записанных в регистрах LED0 ... LED7, произойдет следующий декремент содержимого регистра счетчика количества больших колец индикации Count и т.д., до тех пор, пока в регистре Count не "появится" 0.
После этого, команда goto CYCLE будет проигнорирована (вместо нее - "виртуальный" NOP) и, далее, будет исполнена команда 2-го выравнивающего NOPа.
Все что говорилось выше, относительно 1-го выравнивающего NOPа, справедливо и для 2-го выравнивающего NOPа, и останавливаться на этом я не буду.
После 2-го выравнивания, рабочая точка программы выходит из группы ПП динамической индикации (завершение полного цикла динамической индикации) и, после исполнения групп ПП и команд, осуществляющих различные операции, "уходит" (goto Bin2_10) на первую команду ПП Bin2_10, являющейся точкой входа в полное кольцо программы, после чего, все, что описано выше, будет повторяться до тех пор, пока не будет выключено питание устройства.
Выход на следующее большое кольцо индикации происходит сразу же после смены содержимого регистра Index с .07 (активация 8-го индикатора линейки) на .08 (начало перехода на активацию 1-го индикатора линейки).
"Сразу же" означает то, что этот выход происходит очень быстро, но не "мгновенно".
Посмотрите в текст "программы".
После команды инкремента содержимого регистра Index и до команды сброса в 0 его содержимого (в том случае, если результат инкремента равен .08), "проходит" 6 м.ц.
Таким образом, сброс восьмерки в 0 произойдет через 6 м.ц. (в начале 7-го), то есть, очень быстро.
А теперь несколько слов о "скользком" вопросе, связанным с точкой входа в полное кольцо программы (в примере - первая команда ПП Bin2_10).
Почему после исполнения групп подпрограмм и команд, осуществляющих различные операции, происходит безусловный переход в ПП Bin2_10, а не в ПП START, ведь с точки зрения надежности работы "программы" (об этом говорится в разделе "Дополнительно"), такой переход лучше осуществлять в ПП START?
Если в этой группе подпрограмм и команд, а также везде, где Вы видите строки из точек (в рабочей части программы), имеется команда (команды) goto START, то можно сделать так, как это сделано в тексте "программы", но если таких команд нет, то ... - более подробно этот вопрос будет рассмотрен далее.

Второй этап

 Теперь разберемся с группами команд, которые в тексте группы подпрограмм динамической индикации отмечены прерывистыми линиями из точек, то есть, полностью оформим текст группы подпрограмм динамической индикации для случая применения внешнего дешифратора адресного кода.
Вашему вниманию предоставляется ASM-файл универсальной "заготовки" группы подпрограмм 8-разрядной динамической индикации с использованием внешнего дешифратора Dinam.asm.

Файл Dinam.asm Вы можете скачать: здесь.

Пугаться объема этой группы подпрограмм не стоит. Часть ее мы уже разобрали, а "остаток" подробно "разложим на молекулы сейчас".
Начнем с "шапки".
Вы видите, что в ней дополнительно "прописаны" 2 регистра специального назначения (регистры управления защелками портов А и В) и один регистр общего назначения Temp, который выполняет функции многоцелевого регистра оперативной памяти ("Фигаро здесь, Фигаро там"), в который, по ходу исполнения группы подпрограмм динамической индикации, будут закладываться константы различного функционального предназначения.
И еще один новый "прибамбас": названия также можно присвоить и константам.
Собственно, объяснять тут особо и нечего, все и так понятно из текста "шапки".
Переходим к рабочей части "программы".
После отработки ПП TABLE, возврат по стеку происходит на первую команду группы команд установки запятой movwf Temp.
Эта группа команд позволяет выставлять десятичную запятую в любом разряде линейки для "привязки" к единице измерения (например, Гц, кГц, мГц, мкс, мс, ...).
Предположим, запятую нужно выставить в 5-м разряде линейки 7-сегментных индикаторов.
Чтобы не было путаницы, сразу обращаю Ваше внимание на то, что, в данном случае, говоря о разрядах линейки, подразумевается их нумерация с 0-го по 7-й.
Если "привязаться" к обывательской нумерации (с 1-го по 8-й), то 5-й разряд будет соответствовать 6-му 7-сегментному индикатору линейки.
Анализируем то, что мы имеем после возврата по стеку.
После возврата по стеку, в регистре W, в зависимости от того, какое из 8-ми малых колец индикации является текущим, будет записано какое-либо из 10-ти двоичных чисел в кодировке, позволяющей, соответственно, отобразить какое-либо одно из 10-ти десятичных чисел в активированном разряде линейки 7-сегментных индикаторов.
Если речь идет о 5-м разряде, то, на 5-м "витке" малого кольца индикации, нужно изменить указанную выше кодировку таким образом, чтобы, не воздействуя на секторы 7-сегментного индикатора с названиями A, B, C, D, E, F, G (иначе будет индицироваться символ, не соответствующий результату измерения), выставить 1 в секторе H ("загорание" запятой).
На остальных 7-ми "витках" малого кольца индикации процедура установки запятой должна игнорироваться.
Это есть ни что иное как 2 сценария исполнения программы, следовательно, должна быть применена команда ветвления, проверяющая состояние "чего-то".
Если речь идет о выборе какого-то числа из группы чисел и "привязке" к этому числу одного из 2-х сценариев работы программы (а это так и есть), то первое, что приходит на ум, это то, что, в качестве этого "чего-то", должен быть задействован флаг нулевого результата Z, а байт-ориентированная команда (числа от .00 до .07 отображаются в байте, а не в бите) должна анализировать состояние этого флага (команда btfsc или btfss).
Если задействуется флаг Z, то есть, осуществляется "привязка" к нулевому или не нулевому результату исполнения некой команды, влияющей на состояние этого флага, то одним из результатов исполнения этой команды должен быть ноль.
Это мы уже проходили при разборках с флагом Z (команда subwf).
Итак, необходимо выполнить следующую последовательность действий (см. текст "программы"):
возврат по стеку должен произойти на команду копирования содержимого регистра W в, предварительно назначенный под такого рода операцию, регистр оперативной памяти Temp при помощи команды movwf Temp.
Необходимость этого обусловлена тем, что регистр W будет задействован в дальнейшем, и если не принять мер по сохранению его содержимого, то оно будет "потеряно".
Далее все происходит так, как Вам уже известно:
так как содержимое регистра W сохранено в регистре Temp, регистр W как бы освобождается и в него можно записать константу .05 (movlw 5) для последующего ее использования в операции вычитания.
Для разнообразия, в команде movlw 5, я указал константу без атрибутов систем исчисления (о такой возможности говорилось ранее), но можно "настучать" и .05 или 05h, или b'00000101'.
Далее, командой bsf Status,Z флаг Z поднимается.
Почему поднимается, а не опускается?
Это я делаю в обучающих целях, чтобы акцентировать Ваше внимание на том, что флаг Z является флагом 1-й группы (кто подзабыл - вернитесь назад).
В данном случае, при наличии пятерки в регистре Index, после исполнения команды subwf Index,W, состояние флага Z не изменится (подтверждение ранее установленного состояния), что не совсем удобно при отслеживании программы.
По большому счету, команда bsf(bcf) Status,Z вообще не нужна, но я рекомендую Вам все-таки опустить (заменить в тексте "программы" команду bsf Status,Z на команду bcf Status,Z) флаг Z перед исполнением команды subwf Index,W для удобства отслеживания программы, хуже от этого не будет.
После исполнения команды subwf Index,W, флаг Z будет либо опущен (если в регистре Index числа .00, .01, .02, .03, .04, .06, .07), либо поднят (если в регистре Index число .05).
Если флаг Z поднят, то, через "виртуальный" NOP, будет исполнена команда bsf Temp,7.
Вспоминаем: в регистре Temp "лежит" одно из 10-ти двоичных чисел, в кодировке, позволяющей отобразить одно из 10-ти десятичных чисел в разрядах линейки 7-сегментных индикаторов.
Устанавливаем 1 в бите № 7 этого двоичного числа (bsf Temp,7), что соответствует высвечиванию запятой ("загорается" сектор H 5-го разряда линейки) при отсутствии воздействия на индицируемый символ (любой) десятичного числа (на секторы A, B, C, D, E, F, G), так как состояния битов регистра Temp с №№ с 0 по 6 не меняются.
После исполнения команды bsf Temp,7, рабочая точка программы переходит на 1-ю команду ПП No_Dot.
Если флаг Z опущен, то, при помощи команды goto No_Dot, рабочая точка программы переходит на 1-ю команду ПП No_Dot, как бы "перескакивая" команду bsf Temp,7.
В этом случае, запятая не может быть выставлена.
Вывод: в 5-м разряде линейки 7-сегментных индикаторов, запятая будет высвечиваться одновременно с высвечиванием индицируемого десятичного символа, а в остальных разрядах запятые высвечиваться не будут.
Как изменить положение запятой?
Очень просто: в команде movlw 5, пятерку нужно заменить на номер разряда линейки, в котором эту запятую нужно выставить.
Все, запятая выставлена. Переходим к исполнению группы команд вывода десятичной цифры на индикацию.
Сразу может возникнуть вопрос: "Почему группу команд вывода десятичной цифры на индикацию и группу команд формирования адресного кода управления дешифратором я поменял местами, ведь в тексте "программы" Fsr.asm все наоборот?"
Можно было бы и не менять их местами, но, в этом случае, необходимо было бы применить целых 3 лишних команды безусловных переходов с выставлением метки, без которых вполне можно обойтись, поменяв местами указанные выше группы команд.
Негативных последствий от применения такой перестановки не будет, так как за такое короткое время высвечивания символа в 7-сегментном индикаторе, как 2 мкс. (2 команды по 1 м.ц., кварц 4 мГц.), человек не может визуально его воспринять.
Идем дальше.
Также, как "все пути ведут в Рим", оба сценария выполнения группы команд установки запятой, "уперлись" в 1-ю команду ПП No_Dot (movf Temp,W).
Еще раз напоминаю, что в регистре Temp находится двоичное число, предназначенное для отображения в 7-сегментном индикаторе в виде десятичного символа (результат кодировки).
После исполнения команды movf Temp,W, это число копируется в регистр W (сразу его скопировать в защелки порта В нельзя - только через регистр W) и далее, из регистра W - в защелки (во все 8 штук) порта В (movwf PortB).
Таким образом, на анодах светодиодов линейки "появился" один из "наборов уровней", соответствующий одному из 10-ти десятичных символов (цифры c 0 до 9), который нужно индицировать в одном из разрядов линейки на текущем "витке" малого кольца индикации.
Какой именно разряд линейки будет активирован, зависит от того, каким будет значение адресного кода, который управляет внешним дешифратором, а оно (значение), в свою очередь, зависит от того, на каком именно из 8-ми "витков" большого кольца индикации находится рабочая точка программы.
Как раз этим "делом" и занимается группа команд формирования адресного кода.
Задача сводится к выводу в первые 3 защелки порта А числа (от .00 до .07), "находящегося" в регистре Index.
Для этого достаточно всего двух команд.
Так как, опять же, запись данных в регистр порта (любого) производится через аккумулятор (W), то сначала в регистр W копируется содержимое регистра Index (movf Index,W), а потом содержимое аккумулятора копируется в защелки порта А (movwf PortA).
Естественно, первые 3 вывода порта А (они подключены к адресным входам внешнего дешифратора), перед исполнением группы подпрограмм динамической индикации, должны быть настроены на работу "на выход".
После выхода рабочей точки программы из группы ПП динамической индикации, их можно хоть несколько раз перестраивать под решение других задач, но только после этого выхода.
Настройка остальных 2-х выводов порта А для работы группы ПП динамической индикации не важна (они в ней не задействуются), но эти выводы могут быть как-то задействованы в "группах подпрограмм и команд, осуществляющих различные операции", что на практике и происходит (хотя бы для того, чтобы организовать "общение" между ПИКом и внешним источником сигнала, какой-то из параметров которого необходимо измерить и визуально отобразить в линейке).
Идем дальше.
После того, как адресный код управления дешифратором сформирован ("зажегся" один из разрядов линейки и в нем индицируется "то, что нужно"), рабочая точка программы устанавливается на первую команду группы команд задержки ("чистой" задержки - см. выше).
Эта группа команд есть не что иное, как стандартная ПП задержки без "врезок", реализующая простейший одноразрядный (однобайтный) счетчик.
Ее мы уже разбирали ранее, так что остановлюсь только на особенностях.
Команда movlw обращается к константе не напрямую, а через ее название, "прописанное" в "шапке" "программы".
Регистр Temp последний раз задействовался в группе команд вывода десятичной цифры на индикацию.
Свою функцию он при этом исполнил, и, на момент начала группы команд задержки, он является как бы "свободным".
А раз это так, то его можно повторно задействовать, но уже для "закладки" в него (через регистр W) константы Const1, что и сделано.
Подобную многофункциональность я и называю "Фигаро здесь, Фигаро там".
С регистрами Index или Count подобные манипуляции проделывать нельзя, так как они задействованы на одной функционально обособленной операции в течение большей части цикла динамической индикации.
Изменяя величину константы Const1 (это делается в "шапке"), можно "грубо" регулировать время прохождения рабочей точкой программы малого кольца индикации, а, соответственно, и большого.
Можно ли не присваивать константе названия Const1 (а также и Const2), а указать величину константы непосредственно в команде (movlw ...)?
Конечно же можно. В этом случае, нужно убрать из "шапки" соответствующие "прописки".
Идем дальше.
Следующие несколько групп команд я пропускаю по причине того, что их работа была рассмотрена на 1-м этапе.
Останавливаемся на группах подпрограмм и команд, осуществляющих различные операции.
Если необходимо сформировать калиброванный интервал времени, в который входит время полного цикла динамической индикации (занимает основную часть этого интервала), то, значение константы Const1 ("грубо") нужно подобрать таким образом, чтобы полный цикл динамической индикации был немного меньше, чем расчетная величина калиброванного интервала времени, и затем точно "довести" время полного цикла динамической индикации до этой расчетной величины, увеличив его на величину разницы между значением расчетной величины калиброванного интервала времени и временем полного цикла динамической индикации.
Такая точная "доводка" осуществляется сразу же после выхода рабочей точки программы из группы ПП динамической индикации (после окончания полного цикла динамической индикации).
Посмотрите в текст "программы".
Группа команд точной "доводки", по своей конструкции, это, практически, то же самое, что и группа команд грубой "доводки".
Естественно, они отличаются значениями закладываемых констант и названиями ПП задержки.
Регистр Temp, на момент начала процедуры точной "доводки", "свободен" и, по этой причине, в очередной раз, задействуется для организации исполнения еще одной отдельной функциональной операции.
Если возможны допуски и такой точно откалиброванный интервал времени не нужен, то группу команд точной "доводки" из текста "программы" можно исключить.
Как было упомянуто ранее, после выхода рабочей точки из группы ПП динамической индикации, один из 7-сегментных индикаторов линейки будет находиться в активном состоянии, вплоть до начала следующего цикла динамической индикации, что ухудшает визуальное восприятие показаний линейки.
Чтобы "выправить этот дефект", нужно просто его "погасить", то есть, установить все защелки (байт) порта В в 0.
Для этого и нужны 2 команды группы команд гашения активного разряда линейки (movlw 0 и movwf PortB).
После этого можно считать, что группы подпрограмм и команд динамической индикации сформированы и всему этому "сооружению" можно присвоить статус подпрограммы 8- разрядной динамической индикации с использованием внешнего дешифратора (а кому-то может быть удобнее называть это группой ПП - дело вкуса).
В данном случае, эта ПП динамической индикации оформлена в виде универсальной "заготовки" "врезанной" в полное кольцо некой, "программы", команды которой представлены в неявном виде (строки из точек).
Обратите внимание на несколько строк из точек, которые расположены после последней команды группы команд гашения активного разряда линейки.
Именно в этом "месте" и должно формироваться двоичное число, десятичное значение которого и будет индицироваться в линейке из 8-ми 7-сегментных индикаторов.
Если линейка 8-разрядная, то необходимо сформировать 4-байтное двоичное число (для 7-разрядной - 3-байтное. Почему? Об этом говорилось ранее).
Для данной "заготовки" "программы", которая представлена в общем виде (подробно расписано только то, что относится к динамической индикации), не имеет значения, каким именно образом будет сформировано 4-байтное двоичное число, главное - конечный результат (чтобы оно было сформировано).
Вариантов его формирования может быть множество. Их выбор зависит от функционального предназначения разрабатываемого устройства и выбранного способа его аппаратной и программной реализации.
Разрядность линейки 7-сегментных индикаторов можно изменить.
Если требуется количество десятичных разрядов меньшее чем 8, то, в группе команд "Увеличение на 1 содержимого счетчика ...", нужно заменить значение константы .08 на число десятичных разрядов, которое требуется (.07, .06, ...).
Например, конструируется устройство с использованием 4-разрядной линейки.
Заменяем .08 на .04.
После этого, в группе команд установки запятой, нужно заменить пятерку на тот номер десятичного разряда, в котором нужно выставить запятую и определиться с времязадающими константами X, Const1 и Const2.
Строки с "пропиской" регистров LED4 ... LED7 из "шапки" нужно убрать.
Если запятую выставлять не нужно, то, все команды группы команд установки запятой и первую команду группы команд вывода десятичной цифры на индикацию (вместе с названием подпрограммы (метки) No_Dot) из текста группы ПП динамической индикации можно убрать.
В этом случае, возврат по стеку из ПП TABLE будет происходить на команду movwf PortB, то есть, загрузка данных в защелки порта В будет происходить непосредственно из регистра W, минуя регистр оперативной памяти Temp.
В случае необходимости увеличения количества десятичных разрядов линейки свыше 8-ми, все то же самое, за исключением того, что в этом случае, потребуется наращивание разрядности адресного кода с трех до четырех (задействование под это дело дополнительного вывода порта А) и замена дешифратора 555ИД7 на дешифратор, имеющий более 8-ми выходов и 4 адресных входа.
По выходам, этот дешифратор должен, также как и 555ИД7, "футболить" ноль.
В ЧМ/ЦШ А.Денисова, такая манипуляция "не пройдет", так как вывод порта А RA3 задействован под блокировку счета и выводить на него сигнал старшего разряда адресного кода, в процессе счета (процессы счета и динамической индикации совмещены), нельзя.
В этом случае, потребуется микроконтроллер с большей емкостью порта А, и, если эта задача будет решена, то на индикацию можно будет вывести (при использовании дешифратора с 16-ю выходами) до 16-ти десятичных разрядов (с соответствующей корректировкой "байтности" исходного двоичного числа, количества регистров LED и т.д. - см. выше).
С описанной выше универсальной "заготовкой" вполне можно работать, но, "для полного счастья", не хватает "встраивания" в нее группы подпрограмм преобразования двоичных чисел в двоично-десятичные (ПП Bin2_10).
Об этом пойдет речь в следующем разделе.
Переходите к следующему разделу.


Kea_36

16. Преобразование двоичных чисел в двоично-десятичные. Окончательное формирование текста подпрограммы динамической индикации.

Работа группы подпрограмм преобразования двоичных чисел в двоично-десятичные достаточно сложна для понимания и, скорее всего, для начинающих, детальный "разбор этих полетов" будет связан с немалыми трудностями.
С другой стороны, "разборки" с группой подпрограмм преобразования двоичных чисел в двоично-десятичные, абсолютно необходимы, так как, если речь заходит об операциях с десятичными числами (в большинстве случаев, об их отображении), то без этой группы подпрограмм просто не обойтись.
С одной стороны, если я просто "выдам" готовую группу ПП, которой можно пользоваться, она будет подобна "бесплатному сыру" ("расплата" наступит при первом же затруднении), а, если я "уйду" в детали, то, с непривычки, может получиться "перенапряг".
Так что, если речь идет о какой бы то ни было эффективности обучения, то, хочешь - не хочешь, а компромисс необходим.
Так я и сделаю, а, заодно, попутно, разберемся и с некоторыми "новенькими штучками" и приобретем полезные навыки.
Файл группы подпрограмм преобразования двоичных чисел в двоично-десятичные (далее, для краткости, просто "группы ПП преобразований") я оформил как некую "программу" Bin2_10.asm, то есть, группа ПП преобразований работает в комплексе с группой ПП динамической индикации, а обе они, в свою очередь, "органически вписаны" в "программу", реализующую, например, некое измерительное устройство (частотомер, измеритель RLC, измеритель напряжения и т.д.) с выводом результатов измерения в линейку из 8-ми 7-сегментных индикаторов.
По большому счету, в данном случае, неважно, что измеряется. Главное - чтобы, на каждом "витке" полного цикла "программы", к моменту "входа" рабочей точки программы в группу ПП преобразований, тем или иным способом, было сформировано 4-байтное двоичное число, с которым и будет работать группа ПП преобразований, а в дальнейшем - группа ПП динамической индикации.

Файл Bin2_10.asm Вы можете скачать: здесь.

Сначала разберемся с общей конструкцией "программы".
В начале исполнения "программы", рабочая точка "программы" начинает свое движение по "линейному" участку ПП START, начиная с 1-й ее команды и до команды call Bin2_10.
После исполнения команды call Bin2_10, рабочая точка программы "прыгает" на 1-ю команду ПП Bin2_10, что означает начало исполнения группы ПП преобразований.
При этом, адрес следующей команды (следующей за командой call Bin2_10) закладывается в стек и находится там до окончания исполнения группы ПП преобразований (команда return).
По окончании исполнения группы ПП преобразований (после исполнения команды return), из стека "выгружается" указанный выше адрес и, после "отработки" всех оставшихся команд ПП START, начинается исполнение группы ПП 8-разрядной динамической индикации (то, о чем шла речь в предыдущем разделе).
После "отработки" группы ПП 8-разрядной динамической индикации, рабочая точка "программы" переходит в группу ПП формирования 4-байтного двоичного числа.
Обращаю Ваше внимание на то, что этот переход происходит без использования команд переходов, а как бы "сам собой" в том смысле, что, на "линейном" участке программы, просто исполняется следующая команда ("скачек" не требуется), а, в данном случае, "стык" двух групп подпрограмм находится именно на таком участке.
Конструкций групп подпрограмм формирования 4-байтного двоичного числа может быть множество и это зависит от того, какое именно устройство обслуживает "программа" (частотомер, измеритель RLC и т.д.).
Главное, чтобы тем или иным способом, в конце этой группы ПП, было сформировано 4-байтное двоичное число.
После того, как это произошло, осуществляется безусловный переход на метку NEW, входящую в состав ПП START, для последующего перехода по стеку на первую команду группы ПП преобразований.
Таким образом, мы "прошли" по кольцу полного цикла "программы".
По ходу исполнения "программы", таких колец может быть "намотано" множество, и это зависит от величины времени, в течение которого устройство находится во включенном состоянии.
Разбираемся с "непонятками".
Вопрос: "Зачем осуществлять безусловный переход на метку NEW? Ведь, если этого не делать (команда goto NEW, а, соответственно, и метка NEW, отсутствуют), то, из группы ПП формирования 4-байтного двоичного числа, в группу ПП преобразований, рабочая точка программы перейдет как бы "сама собой" ("стык" находится на "линейном" участке "программы")?
Ответ: да, таким образом рабочая точка программы в группу ПП преобразований "войдет", но "выйти" из нее она не сможет, так как стек, в данном случае, будет пустой (куда переходить? ...???).
А пустой он будет потому, что, перед исполнением группы ПП преобразований, в него не "заложился" адрес возврата, а, проще говоря, не исполнена команда call.
Именно для того, чтобы обеспечить такого рода "закладку" и необходимо, после исполнения группы ПП формирования 4- байтного двоичного числа, выполнить команду goto NEW с тем, чтобы, в дальнейшем, была исполнена команда call Bin2_10.
Проще говоря, для того чтобы, после "отработки" группы ПП формирования 4-байтного двоичного числа, "беспроблемно войти" (а самое главное - "выйти") в группу ПП преобразований, нужно "вернуться назад" и исполнить команду call.
Пришла пора разобраться с ПП START.
Ранее я обращал Ваше внимание на вопросы надежности работы программы, связанные с переходами в ПП START после окончания полного цикла программы.
А теперь более конкретно:
С точки зрения надежности работы программы, конечно же, лучше полный цикл программы начинать с исполнения ПП START, а не с первой команды другой ПП или команды, отмеченной меткой, расположенной "ниже" по тексту программы.
В противном случае, группа команд, на "участке" от 1-й команды ПП START и до первой команды этой другой ПП или команды, отмеченной меткой, будет исполнена всего один раз (при включении питания устройства) и, в ходе дальнейшей работы программы, ее рабочая точка в эту группу команд "входить" не будет (новые полные циклы программы будут формироваться вне "участка" этой группы команд).
В этом случае, при неконтролируемом изменении содержимого регистров, с которыми происходит работа на этом "участке", например, в результате какого-то сбоя, программа может просто перестать работать (зависание) в течение длительного времени (пока включено питание), после чего потребуется выключить и включить питание устройства, что совсем не удобно.
Конечно же, если приняты меры по недопущению таких сбоев, то программа будет работать нормально, но "береженого Бог бережет" и лучше перестраховаться, и особенно в случаях, когда программа предназначена для массового тиражирования.
Пусть лучше некоторые команды, которые достаточно исполнить только один раз, будут многократно исполняться, но, в этом случае, если даже и произойдет сбой на текущем "витке" полного цикла программы, то, на следующем "витке" полного цикла программы, последствия сбоя будут ликвидированы за счет автоматического приведения содержимого "поврежденных" регистров к норме без манипуляций с выключателем питания.
Насколько я понимаю, в этом "скользком" вопросе единого мнения нет.
Одни программисты исключают из полного цикла программы практически все подготовительные операции ПП START (располагают их по тексту программы выше точки входа в полный цикл программы), что, с точки зрения скорости работы программы и незначительного улучшения каких-то эксплуатационных характеристик, хотя и может дать некий "выигрыш", но, при этом, они сильно рискуют свести этот положительный эффект к нулю (ложка дегтя портит бочку меда) за счет ухудшения надежности работы устройств, в которых используется такая растиражированная программа.
Другие программисты поступают разумнее, включая в полный цикл программы наиболее "ответственные" (на их взгляд, все это субъективно) подготовительные операции ПП START, а остальные из этого цикла исключают.
Реализацию именно такого подхода, в обучающих целях, Вы и видите в тексте "программы" Bin2_10.asm.
"Ответственные" команды подготовительных операций ПП START располагаются ниже точки входа в полный цикл программы (команда call Bin2_10), а команды подготовительных операций, рангом пониже, располагаются выше этой точки.
Это означает, что команды подготовительных операций, располагающиеся выше точки входа в полный цикл программы, будут исполнены всего один раз, при включении питания (и сбросах программы на начало, то есть, на первую команду ПП START: об этом я расскажу позднее, а пока будем считать, что этих сбросов как бы нет) и, в дальнейшем, вплоть до выключения и последующего включения питания устройства, исполняться не будут (естественно, при отсутствии сбоев).
Команды подготовительных операций, располагающиеся ниже точки входа в полный цикл программы, будут исполняться многократно, то есть, на каждом "витке" полного цикла программы.
Честно говоря, я не сторонник обеих этих подходов, и при каждом удобном случае стремлюсь включить в полный цикл программы все команды подготовительных операций, хотя, иногда, особенно в сложных программах, и может возникнуть необходимость дележки на "более ответственных" и "менее ответственных".
Это - третий подход.
В этом случае, если "привязаться" к разбираемой "программе", все команды, располагающиеся по тексту "программы" выше точки входа в полный цикл "программы", перемещаются ниже команды call Bin2_10, метка NEW ликвидируется, а вместо нее "вставляется" название ПП START (START).
Соответственно, нужно заменить команду goto NEW (в конце группы ПП формирования 4-байтного двоичного числа) на команду goto START.
Кто желает, тот может произвести такую корректировку.
А теперь давайте разберемся в сути производимых преобразований двоичных чисел в двоично-десятичные.
Предположим, что группа ПП формирования 4-байтного двоичного числа "сработала как нужно" и такое число сформировано.
Что оно должно из себя представлять?
Это число, по определению, должно быть 4-байтным, то есть, оно должно быть "заложено" сразу в 4 регистра общего назначения (назовем их Timer, но можно и как угодно), изначально выделенных под "закладку" этого числа в начале составления текста "программы".
Если это так, то необходимо распределить между этими регистрами приоритеты разрядности, что и сделано (TimerHH, TimerH, TimerM, TimerL). Ранее об этом мы уже говорили.
Если нужно что-то подсчитать, то речь идет о многоразрядном двоичном счетчике (в нашем случае - о 4-байтном, которому можно поставить в соответствие, например, двоичный счетчик на 8-ми микросхемах 555ИЕ5 (каждая 555ИЕ5 отображает полубайт, а не байт, поэтому таких м/схем нужно 8 штук).
Обращаю Ваше внимание: когда речь идет об этой аналогии, то она относится к конечному результату работы обоих таких двоичных счетчиков, то есть, к форме представления результатов счета.
По этому показателю - аналогия полная, то есть, в обоих случаях, 32-битное двоичное число будет отображаться в стандартном весовом коде.
Что же касается принципов счета, то существуют весьма существенные различия, которые я постараюсь объяснить в следующем разделе.
Итак, перед "заходом" рабочей точки программы в группу ПП преобразований, в группе ПП формирования 4-байтного двоичного числа, должно быть сформировано 4-байтное двоичное число в стандартном весовом коде.
Например, 8-разрядная линейка 7-сегментных индикаторов может отобразить десятичное число не более 99999999.
Двоичный эквивалент этого числа в стандартном весовом коде:
00000101 11110101 11100000 11111111
 Можете удостовериться в этом в программке конвертора систем исчисления, которая у Вас имеется.
А теперь внимание: объясняю разницу между двоичным и двоично-десятичным представлением числа.
Двоично-десятичное число, по определению, не может быть больше 9-ти, из чего следует, что оно должно отображаться только в младшем полубайте байта, а старший полубайт байта всегда должен быть равен нулю (0000 хххх).
Если речь идет о многоразрядном регистре, и с учетом того, что в ПИКах нет 4-битных регистров (только 8-битные) общего назначения, то, например, десятичное число 99999999 не может быть отражено (в двоично-десятичном виде) в 4-х 8-битных регистрах общего назначения.
Для отображения этого числа потребуется 8 регистров общего назначения и число 99999999, в двоично-десятичном виде, будет выглядеть так:
00001001 00001001 00001001 00001001 00001001 00001001 00001001 00001001
Вы видите, что во всех младших полубайтах всех 8-ми регистров "находятся" девятки, а во всех старших полубайтах - нули.
Вот зачем и требуются 8 регистров LED0...7, "прописку" которых Вы видите в "шапке" "программы".
Первые четыре из них (LED0...3) задействуются в группе ПП преобразований как элементы оперативной памяти для хранения промежуточных и конечных результатов преобразований.
Регистры LED4...7 для этого не задействуются. В них, на конечной стадии работы группы ПП преобразований, из младших и старших полубайтов регистров LED0...3, переписываются конечные результаты преобразований для последующего их визуального отображения в 7-сегментных индикаторах с 5-го по 8-й.
В конечном итоге, в младших полубайтах регистров LED0...7, на все время полного цикла группы ПП динамической индикации, фиксируются двоично-десятичные числа, с которыми эта группа ПП и будет работать.
В общем виде, работу ПП преобразований можно описать так:
После "захода" рабочей точки программы в группу ПП преобразований, в предварительно назначенный счетчик "проходов" Count, записывается константа, значение которой равно суммарному количеству битов 4-байтного регистра TimerHH/TimerH/TimerM/TimerL (в нем находится 4-байтное двоичное число, которое нужно преобразовать в двоично-десятичное), то есть, значение этой константы, в данном случае, должно быть равно .32.
Содержимое счетчика "проходов" Count, на каждом из 32-х "витков" полного кольца группы ПП преобразований, декрементируется и, если результат декремента не равен 0, то происходит переход рабочей точки программы на следующее полное кольцо группы ПП преобразований.
Таким образом, войдя в группу ПП преобразований, рабочая точка программы "наматывает" 32 полных кольца группы ПП преобразований, которые, в сумме, составляют полный цикл группы ПП преобразований, и, только после этого, выходит из группы ПП преобразований по сценарию "программа исполняется далее" (содержимое счетчика "проходов" Count равно нулю).
Что же происходит при прохождении рабочей точкой программы каждого из 32-х полных колец группы ПП преобразований?
Сначала происходит групповой циклический сдвиг влево содержимого 8-байтного регистра TimerL/TimerM/TimerH/TimerHH/LED0/LED1/LED2/LED3 через бит С регистра STATUS.
Перед первым таким циклическим сдвигом (в начале исполнения полного цикла группы ПП преобразований), в регистры LED0...7 записываются нули, и они, образно выражаясь, "пусты".
После каждого такого группового (последовательный сдвиг содержимого 8-ми байтов) циклического сдвига, кроме того, что происходит циклический сдвиг содержимого обоих 4-байтных регистров TimerL/TimerM/TimerH/TimerHH и LED0/LED1/LED2/LED3, через бит С регистра STATUS, происходит перенос содержимого старшего бита регистра TimerHH в младший бит регистра LED0.
И это вполне закономерно, так как циклический сдвиг влево происходит в пределах указанного выше 8-байтного регистра (8 последовательно исполняемых команд RLF).
Если это кому-то трудно себе представить, то вспомните "бегущую строку" или "бегущие огни".
Смысл такого сдвига заключается в постепенном перемещении (в течение 32-х полных колец группы ПП преобразований) содержимого 4-байтного регистра TimerL/TimerM/TimerH/TimerHH в 4-байтный регистр LED0/LED1/LED2/LED3 с соблюдением приоритетов разрядности.
Если просто, постепенно переместить содержимое указанных выше 4-байтных регистров из одного в другой, то это ничего полезного не даст, так как конечным результатом такой операции будет все то же двоичное число.
Следовательно, после очередного циклического сдвига, необходимо каким-то образом преобразовать содержимое 4-байтного регистра LED0/LED1/LED2/LED3 так, чтобы, к моменту выхода рабочей точки программы из группы ПП преобразований (счетчик проходов Count очищен), во всех 8-ми полубайтах 4-байтного регистра LED0/LED1/LED2/LED3 были записаны двоично-десятичные числа с соблюдением приоритетов разрядности.
Таким образом, после операции циклического сдвига, необходимо исполнить процедуру типа "чтение - модификация - запись".
Это означает, что, после очередного циклического сдвига, содержимое текущего регистра LED анализируется на соответствие или несоответствие определенным условиям с целью последовательной модификации двоичных чисел, записанных в регистрах LED0, LED1, LED2, LED3 таким образом, чтобы, на момент окончания полного цикла группы ПП преобразований, во всех полубайтах этих регистров были записаны двоично-десятичные числа с соблюдением приоритетов разрядности.
Так как, по окончании текущего цикла "чтение - модификация - запись", запись модифицированных (преобразованных) чисел производится в те же регистры, из которых производилось чтение (LED0...3), то, при модификации, должен задействоваться регистр оперативной памяти (в нашем случае - Mem), в котором будут храниться как промежуточные, так и конечные результаты преобразований чисел.
По окончании текущего цикла модификации, в регистре оперативной памяти Mem будет записано модифицированное число, которое затем записывается в текущий LED.
После того, как рабочая точка программы "намотает" 32 полных кольца группы ПП преобразований, во всех 8-ми полубайтах 4-байтного регистра LED0/LED1/LED2/LED3 будет записано 8 двоично-десятичных чисел, точно отражающих значение 32-битного двоичного числа, находившегося в 4-байтном регистре TimerHH/TimerH/TimerM/TimerL на момент начала исполнения группы ПП преобразований, с соблюдением приоритетов разрядности.
Остается только "раскидать" указанные выше 8 полубайтов регистра LED0/LED1/LED2/LED3 (также с соблюдением приоритетов разрядности) по младшим полубайтам регистров LED0, LED1, LED2, LED3, LED4, LED5, LED6, LED7, и установить в их старших полубайтах нули (см. приведенный выше пример с двоично-десятичным представлением числа 99999999).
Все, 8-разрядное двоично-десятичное число сформировано (находится в регистрах LED0...7) и с ним можно работать в группе ПП динамической индикации.
Не смотря на высокую сложность происходящих в группе ПП преобразований процессов, работать с этой группой ПП достаточно просто: в "шапке" "программы" предварительно назначаются и "прописываются" 2 регистра общего назначения - счетчик "проходов" и регистр оперативной памяти (в нашем случае - Count и Mem ).
В начале группы ПП преобразований, нужно записать в счетчик "проходов" число бит преобразуемого двоичного числа.
Например, если нужно отобразить в линейке из 4-х 7-сегментных индикаторов 4-х разрядное десятичное число (максимум - 9999), то, для его отображения, в двоичном виде, достаточно двух регистров общего назначения (например, TimerM и TimerL).
Таким образом, в счетчик числа "проходов", в начале исполнения группы ПП преобразований, необходимо записать константу .16.
Лишние LEDы (LED4...7) и группы команд, производящих операции с этими LEDами, нужно убрать, а оставить только регистры LED0, LED1, LED2, LED3 и те группы команд, которые "работают" с их содержимым.
В группе ПП преобразований (до поразрядного распределения содержимого обоих полубайтов регистров LED0 и LED1 по младшим полубайтам регистров LED0...4) нужно использовать только регистры LED0 и LED1.
Сказанное выше предполагает "изъятие" из текста предлагаемой Вашему вниманию "программы" некоторых команд.
Для того чтобы стало понятно, какие именно команды нужно "изымать" или добавлять (в случае работы с двоичными числами, отображаемыми более чем в 4-х байтах), нужно более детально разобраться, как работает группа ПП преобразований.
Группа ПП преобразований начинается с подготовительных операций.
Флаг переноса - заема С опускается (бит С регистра STATUS сбрасывается в 0), в счетчик проходов Count записывается константа, значение которой равно общему количеству битов 4-байтного двоичного числа (.32) и содержимое всех регистров LED сбрасывается в 0.
Далее рабочая точка программы "входит" (ее движение, пока, происходит по "линейному" участку) в ПП Loop16.
В ней, сначала, происходит циклический сдвиг влево на одну позицию, через бит С регистра STATUS, содержимого 8-разрядного регистра TimerL/TimerM/TimerH/TimerHH/LED0/LED1/LED2/LED3 (первые 8 команд rlf), а потом, содержимое счетчика проходов Count декрементируется с сохранением результата декремента в нем же.
Так как байт-ориентированная команда decfsz является командой ветвления, то, если результат декремента равен 0, то рабочая точка программы "уйдет" в группу команд поразрядного распределения содержимого регистров LED0...3 (обоих полубайтов) по младшим полубайтам регистров LED0...7 (на конечной стадии процесса преобразований).
Этой группе команд вполне можно было бы "присвоить статус" отдельной подпрограммы, но, так как обращений к 1-й команде этой группы команд в тексте "программы" нет (нет команд переходов на 1-ю команду этой группы команд), то она входит в состав ПП Loop16.
Если результат декремента не равен 0, то осуществляется безусловный переход в ПП adjDEC.
В состав ПП adjDEC входит 4 группы команд с условными переходами в ПП adjBCD и последующим возвратом по стеку (после отработки ПП adjBCD) на следующую (из этих четырех) группу команд.
Каждая из этих групп команд работает со "своим" регистром LED (с нулевого по третий) с соблюдением приоритетов по разрядности.
То есть, в начале ПП adjDEC, происходит "работа" с содержимым регистра LED0, а в конце ПП adjDEC - с содержимым регистра LED3.
Как только с содержимым регистра LED3 будут осуществлены необходимые операции, то осуществляется безусловный переход в ПП Loop16, то есть, "выход" на следующее полное кольцо ПП преобразований (всего таких колец 32).
На каждом таком кольце, осуществляется перенос (через бит С регистра STATUS) значения старшего бита регистра TimerHH в младший разряд регистра LED0 (сдвиг содержимого 8-байтного регистра TimerL/TimerM/TimerH/TimerHH/LED0/LED1/LED2/LED3 на одну позицию).
ПП adjDEC никаких числовых преобразований не осуществляет, а исполняет функцию "администратора" в том смысле, что определяет порядок обработки содержимого регистров LED0...3.
Не трудно заметить, что он следующий: LED0, LED1, LED2, LED3, то есть, с учетом приоритетов разрядности.
Каждая из указанных выше 4-х групп команд состоит из 3-х команд как бы "разорванной" косвенной адресации.
Слово "разорванной" означает то, что в ПП adjDEC осуществляется только запись в регистр FSR адресов регистров, с содержимым которых будут производиться действия, а сами эти действия будут производиться в другой подпрограмме (в ПП adjBCD).
После исполнения команды movlw LED... (0, 1, 2, 3), в регистр W записывается адрес текущего регистра LED (вспоминайте то, что говорилось ранее о косвенной адресации).
После исполнения команды movlw FSR, этот адрес копируется из регистра W в регистр FSR.
Далее осуществляется условный переход в ПП adjBCD (call adjBCD).
После того, как ПП adjBCD "отработает" (исполнена команда retlw 0), содержимое регистра W сбрасывается в 0 и происходит возврат по стеку на команду записи в регистр W адреса следующего регистра LED, то есть, переход на обработку содержимого следующего по разрядности регистра LED, и так до тех пор, пока не будет осуществлена обработка содержимого последнего регистра LED (LED3).
После этого осуществляется безусловный переход в ПП Loop16, то есть, на новый "виток" полного кольца ПП преобразований (еще раз напоминаю: всего таких колец, в данном случае, 32).
А теперь обратим внимание на стек.
Ранее рассматривались случаи, когда в стек закладывался только один адрес возврата.
В рассматриваемом случае, адрес возврата на 1-ю, после команды call Bin2_10, команду "программы", находится в стеке практически все время полного цикла ПП преобразований.
"Практически все время" означает то, что стек очищается от этого адреса возврата в течение очень малого интервала времени: от момента исполнения команды return (стек очищается) до момента исполнения команды call Bin2_10 (стек загружается).
А если это так, то что происходит, когда в стек дополнительно, последовательно загружается тот или иной адрес возврата при исполнении команд одного из 4-х условных переходов подпрограммы adjDEC?
При загрузке одного из этих адресов возврата, адрес возврата команды call Bin2_10 из вершины стека (из первой строки таблицы стека) "вытесняется" (переходит) во вторую строку таблицы стека, а адрес возврата текущей команды call adjBCD подпрограммы adjDEC занимает вершину стека.
После исполнения команды retlw 0 (ПП adjBCD), адрес возврата текущей команды call adjBCD (ПП adjDEC) из вершины стека "уходит" (исчезает, выгружается, удаляется... - как угодно), и вершина стека освобождается.
А так как в стеке находится еще один адрес возврата (команды call Bin2_10) и "свято место (вершина стека) пусто не бывает", то он (адрес) тут же, автоматически, "переходит" из второй строки таблицы стека в первую (в вершину стека).
Таким образом, если, на момент исполнения текущей команды call, в стеке уже находится адрес (адреса) возврата, заложенный в него ранее, то он (они) смещается по таблице стека (всего в этой таблице 8 строк) на одну позицию вниз, а в вершину стека записывается адрес возврата текущей (последней) команды call.
При удалении из вершины стека адреса возврата, активные строки таблицы стека синхронно смещаются вверх, и в вершине стека будет присутствовать адрес возврата, ранее находящийся во 2-й строке таблицы стека.
Итак, после записи в регистр FSR адреса текущего LED (в ПП adjDEC), происходит условный переход рабочей точки программы в ПП adjBCD.
ПП adjBCD является как бы "основной кухней" преобразования чисел из двоичных в двоично-десятичные.
Она работает с результатами групповых циклических сдвигов (8 последовательных сдвигов), находящимся в регистрах LED0...3.
В начале ПП adjBCD, в регистр W записывается константа 03h (movlw 3), которая, далее, суммируется с содержимым текущего LEDа при помощи команды addwf 0,W.
Эта команда является "хитрой" в том смысле, что ей завершается ранее начатая процедура косвенной адресации без непосредственного обращения к содержимому регистра INDF (вспоминайте ранее пройденный материал).
Получается что-то типа двойной косвенной адресации.
И в самом деле, процедура косвенной адресации уже начата ранее, и в ходе ее, происходит еще одно косвенное обращение к регистру INDF, а именно, в команде addwf 0,W обращение к регистру INDF происходит не напрямую, а через его адрес (0, что соответствует 00h - см. область оперативной памяти).
Если происходит обращение к регистру INDF, то, по определению, происходит обращение к содержимому того регистра, адрес которого записан в регистре SFR.
В регистре SFR находится адрес текущего LEDа (он может быть любым; с LED0 по LED3), следовательно, произойдет суммирование содержимого текущего LEDа c ранее записанной в регистр W константой 03h, с сохранением результата суммирования в регистре W.
Затем этот результат суммирования копируется из регистра W в регистр оперативной памяти Mem и сохраняется там на время исполнения следующей бит-ориентированной команды анализа состояния его бита № 3 (btfsc Mem,3).
Если этот бит равен 1, то исполняется команда movwf 0, которая таже относится к разряду "хитрых".
В ней также происходит обращение не к содержимому регистра, а к адресу регистра INDF (00h), то есть, опять же, к текущему LED.
Таким образом, читать ее нужно так: переслать содержимое регистра W в текущий регистр LED.
А так как в регистре W находится то же самое, что и в регистре Mem (содержимое регистра W ранее копировалось в регистр Mem, и с тех пор изменения содержимого регистра W не было), то команду movwf 0, в конечном итоге, можно неформально интерпретировать как "переслать (скопировать) содержимое регистра Mem в текущий регистр LED (через регистр W)".
Если бит № 3 регистра Mem равен 0, то указанная выше процедура повторяется снова, но только в регистр W будет записываться не константа 03h, а константа 30h, и будет произведен анализ состояния бита № 7 регистра Mem, а не его бита № 3.
Почему 30h, а не .30?
Напоминаю, что, если в команде указано число без атрибутов систем исчисления (а в данном случае так оно и есть), то речь идет о 16-ричной системе исчисления, а не о десятичной.
Исходя из этого, можно, например, команду movlw 30 представить в виде movlw .48 или movlw b'00110000', результат будет одним и тем же.
Если, при втором анализе, бит № 7 регистра Mem будет равен 0, то произойдет возврат по стеку в ПП adjDEC, после чего произойдет смена текущего LEDа, переход по стеку в ПП adjBCD, ее отработка, возврат по стеку в ПП adjDEC и т.д. до тех пор, пока не будет закончена обработка содержимого самого старшего регистра LED (LED3).
После этого происходит безусловный переход в ПП Loop16, то есть, на следующее полное кольцо группы ПП преобразований.
И так - 32 раза (считая от момента исполнения команды clrf LED7).
После того, как содержимое счетчика проходов Count станет равным 0, рабочая точка программы "войдет" в группу команд поразрядного распределения содержимого регистров LED0...3 по младшим полубайтам регистров LED0...7.
На момент этого входа, во всех 8-ми полубайтах регистров LED0...3 будут находиться результаты преобразований в виде восьми 4-битных двоично-десятичных чисел, распределенных по восьми полубайтам в порядке возрастания разрядности многоразрядного двоично-десятичного числа (считая от младшего полубайта регистра LED0 и далее по-порядку).
Исходя из этого, распределение нужно начинать со старшего полубайта регистра LED3 (он "уходит" в младший полубайт регистра LED7) и далее по-порядку, в сторону уменьшения разрядности многоразрядного двоично-десятичного числа, находящегося в полубайтах регистров LED0...3.
В противном случае, если распределение начать с младшего полубайта регистра LED0, содержимое некоторых полубайтов регистров LED1...3 будет утеряно.
Таким образом, должно быть сформировано 8 групп команд распределения, причем, после распределения каждого полубайта, они должны гарантированно обеспечивать запись нуля в старший полубайт регистра-получателя (почему? - см. выше).
Эти 8 групп команд, выполняют одну и ту же "работу", но разными способами: 4 группы команд, работающие с младшими полубайтами регистров LED0...3, - одним способом и 4 группы команд, работающие со старшими полубайтами регистров LED0...3, - другим.
Это обусловлено положением полубайта в байте: если полубайт старший, то необходимо задействовать команду смены местами старшего и младшего полубайтов (swapf), а если полубайт младший, то этого не требуется.
Рассмотрим работу первых двух групп команд (остальные пары групп команд работают аналогично).
Распределение начинается со старшего полубайта регистра LED3.
Командой swapf LED3,W (внимание, работа этой команды ранее не рассматривалась) старший и младший полубайты регистра LED3 меняются местами. Результат этой операции сохраняется в регистре W.
Следующая команда andlw 0Fh (внимание, работа этой команды ранее не рассматривалась) выполняет побитное "И" содержимого регистра W и константы 0Fh (.15 или b'0000 1111), то есть, через регистр W, выполняется побитное "И" содержимого регистра LED3, с учетом смены местами его полубайтов, и константы 0Fh, с сохранением результата этой операции в регистре W.
Смысл применения этих 2-х команд в том, что старший полубайт "встает" на место младшего, а в старшем полубайте гарантированно устанавливается 0.
Это объясняется тем, что, по логике "И", результат побитного "И" с нулем всегда есть ноль, а результат побитного "И" с единицей (первое слагаемое) повторяет состояние второго слагаемого, то есть, значение младшего полубайта регистра W (а оно равно значению старшего полубайта регистра LED3), после исполнения команды andlw, не меняется, а старший полубайт регистра W гарантированно устанавливается в 0.
После этого остается только скопировать содержимое регистра W в регистр LED7, что и делается при помощи команды movwf LED7.
Таким образом, старший полубайт регистра LED3 "ушел" в младший полубайт регистра LED7.
Следующая группа команд работает с младшим полубайтом регистра LED3.
В этом случае, менять местами полубайты регистра LED3 не нужно.
Первая команда (movfw LED3) копирует содержимое регистра LED3 в регистр W.
Важно: movfw - не опечатка. Такой команды в распечатке команд Вы не найдете (есть movwf), но, тем не менее, команда movfw, по факту, существует и реально работает (можете добавить в список).
Работа второй команды (andlw 0Fh) описана выше.
Работа третьей команды (movwf LED6) описана выше, разница только в том, что содержимое регистра W копируется не в регистр LED7, а в регистр LED6.
Таким образом, младший полубайт регистра LED3 "ушел" в младший полубайт регистра LED6, а старший полубайт регистра LED6 установился в 0.
Остальные 3 пары групп команд распределения работают аналогичным образом.
При распределении полубайтов в пределах регистров LED0...3, содержимое ранее распределенных их полубайтов, последовательно "уничтожается" путем копирования в их младшие полубайты содержимого соответствующих полубайтов (старших или младших) регистров с младшей разрядностью (запись "поверх" ранее записанных данных) и установки нолей в старших полубайтах регистров-получателей.
На конечной стадии распределения, младший полубайт регистра LED0 копируется в него же (в чем смысл?).
Содержимое младшего полубайта регистра LED0, казалось бы, можно вообще "не трогать" (не предпринимать с ним операций), но, в этом случае, старший полубайт регистра LED0 не будет установлен в ноль, так что последняя группа команд распределения должна быть выполнена в полном объеме.
После ее исполнения, рабочая точка программы переходит на команду возврата return, и далее, осуществляется возврат по стеку на следующую, после команды call Bin2_10, команду подпрограммы START.
После отработки команд ПП START, начинается исполнение группы ПП 8-разрядной динамической индикации и так далее, до нового "вхождения" рабочей точки программы в группу ПП преобразований.
Примечание: в группе ПП преобразований, ПП adjBCD является как бы "темной лошадкой" в том смысле, что ее работа, с точки зрения производимых действий, вполне понятна, но в комплексе с 32-мя групповыми циклическими сдвигами, получается довольно-таки объемная и трудно воспринимаемая "круговерть" чисел.
Конечно же, можно "погонять" их на бумаге, что я в свое время и проделывал для того чтобы убедиться в факте описанного выше преобразования, но особого практического смысла в этом нет: группа ПП преобразований работает в точном соответствии с описанным выше алгоритмом преобразований и, при правильном ее оформлении, никаких сложностей в "эксплуатации" этой группы ПП нет.
Практический смысл имеет "трансформация" ее текста под замыслы программиста.
Теперь, после того, как стало ясно, "откуда ноги растут", это сделать можно.
Вернемся к случаю "переоборудования" описанной выше группы ПП преобразований, расчитанную на формирование 8-разрядных десятичных чисел в группу ПП преобразований, расчитанную на формирование 4-разрядных десятичных чисел.
В этом случае, необходимо произвести следующие действия:
В "шапке" "программы":
1. Убрать регистры LED4...7, а оставить регистры LED0...3.
2. Убрать регистры TimerH и TimerHH, а оставить регистры TimerL и TimerM.
Таким образом, группа ПП формирования двоичного числа должна быть расчитана на формирование 2-байтного двоичного числа, которое, в конце работы этой группы ПП, должно быть сформировано в регистрах TimerL и TimerM.
В рабочей части "программы":
3. В ПП Bin2_10 заменить константу 8*4=.32 на константу 8*2=.16.
4. В ПП Bin2_10 убрать 4 команды: clrf LED4...7.
5. В ПП Loop16 убрать 4 команды: rlf TimerH,F, rlf TimerHH,F, rlf LED2,F, rlf LED3,F.
6. В группе команд поразрядного распределения убрать первые 4 (сверху) группы команд распределения.
7. В ПП adjDEC убрать 3-ю и 4-ю (сверху) группы команд (movlw, movwf, call) косвенной адресации.
Как видите, такого рода "трансформацию" произвести достаточно несложно, нужно только знать принцип работы подпрограмм и групп команд.
Таким образом можно "подогнать" конструкцию группы ПП преобразований под любую разрядность линейки 7-сегментных индикаторов.
Если учесть, что такого же рода "подгонку" можно произвести и в группе ПП динамической индикации (об этом говорилось ранее), то, в комплексе, владение такой методикой, позволяет программисту "перекраивать" группы ПП динамической индикации и преобразований двоичных чисел в двоично-десятичные под конкретные, решаемые им задачи, то есть, речь идет о некой "универсальной заготовке", включающей в себя как группу ПП динамической индикации, так и группу ПП преобразований двоичных чисел в двоично-десятичные.
А теперь сведем то, что было описано выше по частям, к единому целому.
Получается некая "универсальная заготовка", которая выглядит так:

Файл этой "заготовки" Din_Bin.asm можно скачать: здесь.

Все что Вы видите, было "разобрано" ранее.
Сразу бросается в глаза "разросшаяся" "шапка" "программы", что вполне естественно, так как в ней объединены элементы обеих "шапок", разобранных ранее.
Регистр Count, так же, как и регистр Temp, задействуется многократно (в данном случае, 2 раза: по одному разу в каждой группе ПП).
Если Вы проассемблируете текст "программы" Din_Bin.asm, то получите сообщение о 7-ми ошибках, что вполне закономерно, так как в тексте этой "программы" значения трех констант выражены в символическом виде (X, Y1, Y2). Это 3 "ненормальности", которые MPLAB "воспринимает" как ошибки.
Остальные 4 ошибки связаны с "непонятностью" того, к чему обращается директива CONFIG (....) - 2 ошибки и "непонятностью" первой команды ПП START (....) - 2 ошибки.
Естественно, когда символические константы X, Y1, Y2 будут заменены на числа, директива CONFIG будет обращаться к числу (например 03FF1H) и будет определена 1-я команда ПП START, сообщения об этих 7-ми ошибках выдаваться не будут.
Правила пользования "заготовкой":
Если Вы хотите перейти к другому количеству десятичных разрядов линейки 7-сегментных индикаторов, то руководствуйтесь изложенной ранее информацией, в которой подробно расписаны все связанные с этим действия.
Числовые значения констант X, Y1, Y2 определяет программист, исходя из стоящих перед ним задач.
На что влияют эти числовые значения - также изложено ранее.
Если в этом есть необходимость, то в малое кольцо индикации могут быть "врезаны" одна или несколько подпрограмм или групп команд, не содержащих в себе команд ветвления, результат исполнения которых (команд ветвления) может привести к преждевременному выходу рабочей точки программы из группы ПП динамической индикации.
Допускается "врезка", в малое кольцо индикации, одной или нескольких подпрограмм или групп команд, содержащих команды ветвления, при условии, что все сценарии их исполнения локализованы внутри этих подпрограмм или групп команд, и ни один из них не предусматривает преждевременного выхода рабочей точки программы из группы ПП динамической индикации.
В последнем случае, если речь идет о формировании высокостабильной задержки (интервала времени), временные интервалы исполнения обоих сценариев команды (команд) ветвления должны быть одинаковыми.
О том, как это делается, было рассказано выше (выравнивающий NOP или NOPы).
Если изложенная выше методика "трансформации" данной универсальной "заготовки" освоена и понятна, то единственное, что остается сделать - определить состав команд группы ПП формирования N-байтного (число N определяет программист, исходя из изложенного выше) двоичного числа.
Если число N определено (известно), то, естественно, это должно учитываться при конструировании группы ПП формирования N-байтного двоичного числа.
Состав команд этой группы может быть различным.
Это зависит от того, что и каким способом нужно выразить в форме N-байтного двоичного числа.
В линейке 7-сегментных индикаторов будет отображаться, в десятичной форме представления, то, что выражено в форме N-байтного двоичного числа.
В N-байтное двоичное число можно "загнать" любой параметр внешнего (по отношению к микроконтроллеру) аналогового или цифрового сигнала: амплитуду, частоту, фазу, скважность.
Короче говоря - "все карты в руки", выбор - богатейший.
Таким образом, в этом подразделе "программы" с названием "Группа подпрограмм формирования 4(N) - байтного двоичного числа", "сконцентрирован" очень объемный класс задач, на решение которых, в основном, и направлены основные усилия программиста.
Если говорить о деталях, то объем этого класса задач "бесконечен", но "набор приемов" все-таки конечен, и его освоение - вполне реальная задача.
С частью этих приемов Вы ознакомились, а это уже вполне приличный "старт".
Если Вы, с первого "захода", поняли хотя бы 50% из того, о чем шла речь, это уже несомненый успех.
Программирование - дело "наживное" и "нешапкозакидательское". Повторите "заходы" и рано или поздно "все встанет на свои места".
Мало того, в этом деле не нужно комплексовать по поводу того, что что-то с трудом понимается, это естественно и нормально. "Противник" грозный и живучий, и он, по определению, будет "сопротивляться".
Несколько "голов" у этого многоголового "дракона" мы уже "посшибали", давайте "посшибаем" еще.
Переходите к следующему разделу.


Kea_37

17. Принцип счета. Работа с таймером TMR0. Принцип установки групп команд счета в текст программы.

Как это ни странно, но излагаемая ниже информация почему-то, образно выражаясь, является "страшно секретной" (и прежде всего это относится к информации по работе подпрограммы досчета) в том смысле, что, например, мне, так и не удалось найти не только детального описания этой базовой процедуры, но и вообще никакой более-менее "внятной" информации по высокоскоростному счету (если я ошибаюсь - поправьте), что, в свое время, вынудило меня хорошенько "поднапрячься", так как как-то не солидно заниматься программированием без знания деталей такой базовой процедуры.
Ниже предпринята попытка достаточно подробного (как мне кажется) описания механизма низкоскоростного и высокоскоростного счета, расчитанная на начинающих программистов, усвоивших предыдущие разделы "Самоучителя...".
Низкоскоростной счет
 Первичная информация о таймере TMR0 и принципе счета, основанном на его применении, была изложена ранее.
Казалось бы, все просто: при работе от внешнего источника импульсов, подсчитывается количество переполнений. Оно умножается на 256 и к этому произведению приплюсовывается число, находящееся в TMR0 на момент окончания счета.
Посмотрим, что из этого выйдет.
Сразу "закручиваем гайки", предполагая, что производится подсчет импульсов с максимальной частотой, которую способен посчитать PIC16F84A, то есть, 30 мГц.
Если используется "чистый" TMR0 (без предделителя), то за 1 сек. он переполнится 30000000/256=117187,5 раза, следовательно, если производить отсчет от момента установки содержимого TMR0 в ноль, то TMR0 переполнится через 1/117187,5=0,0000085 сек., то есть, через 8,5 мкс.
Мало того, число переполнений 117188 (округляем в сторону увеличения) невозможно отобразить не только в однобайтном регистре, но даже и в двухбайтном, следовательно, придется применить 3-байтный регистр.
На практике это означает то, что, кроме очень частых проверок на переполнение TMR0, придется организовывать переносы с задействованием операций инкремента.
Проще говоря, программа будет состоять из сплошной череды проверок на переполнение TMR0 и переносов, чего "и врагу не пожелаешь", а еще проще - программа будет "длинной и бестолковой".
По этой причине, "чистый" TMR0 (без предделителя) может применяться для подсчета количества импульсов с частотами меньшими чем 256*256=65536 Гц. (65,536 кГц).
В этом случае, для подсчета количества переполнений, достаточно одного регистра общего назначения и величина интервала времени между двумя соседними переполнениями вполне приемлема (примерно 3,9 мс.).
Запомните эту "частотную границу".
Конечно же, можно, задействовав для подсчета переполнений 2-байтный регистр (в этом случае, нужно организовывать перенос из разряда L в старший разряд), "поднять" эту частотную границу.
В этом случае, "на передний план" выходит величина интервала времени между двумя соседними переполнениями.
Исходя из моего личного опыта, этот интервал времени не следует делать меньшим, чем 1,5 мс., что позволяет, в этом случае, повысить "частотную границу" в 3,9/1,5=2,6 раза, то есть, до частоты 65,536кГц.*2,6=170,3936 кГц.
Округлим этот "верхний потолок" до 200 кГц. и получаем максимальное значение подаваемой на счетный вход "чистого" TMR0 частоты, выше которой, как говорится, "не прыгнешь" без существенного ущерба для качества программы.
Все, "приплыли", дальше - "запретная зона".
200 кГц. конечно маловато и нужно как-то выходить из этого "тупика".
Так что, не так все просто, как может показаться на первый взгляд.
Выход, как это ни странно, математически, довольно-таки простой, но "въезд" в эту "математику" (особенно в части касающейся подпрограммы досчета) часто вызывает затруднения даже у тех программистов, которых нельзя назвать начинающими.
В простейшем случае, просто используется готовый стандартный набор команд счета, а как он работает - "темный лес".
Конечно же, можно пойти и по такому пути, но я Вам советую все-таки как следует разобраться с организацией счета, так как программист, не разобравшийся с этой базовой процедурой, рискует в будущем заполучить массу проблем.
Высокоскоростной счет
 Для того чтобы было понятно, с чем имеем дело, есть смысл кое-что напомнить и добавить.
Таймер TMR0 представляет собой делитель на 256, что означает, что "чистый" TMR0 (без использования предделителя) будет переполняться каждый раз после поступления на его счетный вход (вывод RA4/TOCKI) "пачки" из 256 импульсов (при переходе от .255 к .00).
TMR0 считает только в одну сторону (реверса нет), а именно - в сторону увеличения.
Если в качестве тактового сигнала для TMR0 выбран внешний тактовый сигнал, поступающий на вывод RA4/TOCKI с выхода источника импульсов, то счет (моменты смены чисел в TMR0) "привязан" именно к внешнему тактовому сигналу и зависит только от него.
Это означает, что, в случае выбора внешнего тактового сигнала, внутренний тактовый сигнал ПИКа не оказывает никакого влияния на процесс счета таймера (за исключением случаев его предустановок, то есть, предварительной записи в TMR0 каких-либо чисел. В этом случае происходит синхронизация, то есть, "привязка" внутреннего и внешнего тактов друг к другу).
TMR0 считает всегда, и для того чтобы остановить или разрешить счет, нужно заблокировать или разблокировать счетный вход ПИКа (вывод RA4/TOCKI), то есть, либо "закоротить" его на корпус, либо снять это короткое замыкание соответственно.
Для этого, обычно, выводы RA3 (блокировка вкл./выкл.) и RA4/TOCKI (счетный вход ПИКа) электрически соединяются между собой, и блокировка осуществляется по выводу RA3 с использованием программных средств (0 - блокировка, 1 - разблокировка).
Как это делается - см. принципиальную схему ЧМ/ЦШ А.Денисова).
Критерием переполнения таймера TMR0 является флаг прерывания по переполнению TMR0 (бит № 2 регистра INTCON с названием T0IF).
Установка бита этого флага в 1 свидетельствует о факте переполнения TMR0, а наличие в нем нуля свидетельствует о том, что переполнения TMR0 нет.
В определении флага T0IF упоминаются прерывания.
Этот флаг работает не зависимо от того, разрешены или запрещены прерывания по переполнению TMR0, следовательно, в случае предварительного запрета прерываний по переполнению TMR0 в процессе счета (что и имеет место быть. А какой особый смысл уходить в прерывания в процессе счета, за исключением каких-то уж совсем специфических случаев), этот флаг будет подниматься при переполнении TMR0.
Флаг T0IF относится к флагам 2-й группы, следовательно, в конце процедуры проверки TMR0 на переполнение, обязательно необходимо, для обеспечения нормального прохождения последующих проверок на переполнение, программно сбросить бит этого флага в 0 (см. распечатку регистра INTCON), так как, после его установки в 1, ничем, кроме как программными средствами, сбросить его нельзя.
Перед началом процедуры проверки на переполнение TMR0, в перестраховочных целях, а также в целях обеспечения комфортного отслеживания работы программы в части касающейся процедур счета, также нужно сбросить бит флага T0IF в 0.
Разбираемся с математикой.
Для наглядности, сразу же "привяжемся" к конкретике и рассмотрим организацию счета в ЧМ/ЦШ А.Денисова.
Исходные данные: результаты счета записываются в стандартном весовом коде в 3 регистра общего назначения с названиями TimerH, TimerM, TimerL.
Иными словами, в конце полного цикла счета, в этих регистрах, с соблюдением приоритетов разрядности, "оседает" 3-байтное двоичное число результата измерения.
Интервал времени измерения - 0,1 сек. (100000 мкс.).
Максимальная измеряемая частота - 30 мГц. (определяется максимальной частотой, которую можно подавать на счетный вход PIC16F84A).
Разрядность линейки 7-сегментных индикаторов - 7 десятичных разрядов.
Погрешность измерения - 10 Гц.
Так как легких путей искать не стоит, рассмотрим случай счета импульсов импульсной последовательности с частотой 30 мГц.
Сразу обращаю внимание, что, в этом случае, речь идет не о подсчете 30 000 000 импульсов, а о подсчете 3 000 000 импульсов, так как интервал времени измерения составляет не 1 сек., а 0,1 сек. (отсюда и погрешность в 10 Гц.).
Итак, выше мы убедились, что для такой высокой частоты вариант счета с использованием "чистого" TMR0 не подходит, и в первую очередь потому, что, в этом случае, проверки на переполнение придется производить "неприлично часто".
Выход из положения напрашивается сам по себе: необходимо замедлить счет TMR0, то есть, сделать так, чтобы счетный вход TMR0 "реагировал" не на каждый импульс, поступающий на вывод RA4/TOCKI, а, например, на каждый 10-й, 20-й, 34-й, ... и т.д. (цифры взяты "с потолка").
Соответственно, в 10, 20, 34, ... раз увеличится и время одного цикла переполнения, что и требуется (проверки на переполнение не будут такими частыми, как в исходном случае).
Для этого, между выводом RA4/TOCKI (счетным входом ПИКа) и счетным входом TMR0 необходимо включить делитель на 10, 20, 34, ... .
Такой делитель в ПИКе имеется, он называется предделителем, так что проблем с этим нет.
Посмотрите в распечатку регистра OPTION.
Для того чтобы обеспечить требуемый режим работы, перед началом счета нужно установить его бит № 5 в 1 (выбор внешнего такта с вывода RA4/TOCKI), бит № 4, обычно, устанавливают в 0 (приращение по переднему фронту), бит № 3 устанавливается в 0 (предделитель включен перед TMR0), а с битами №№ 0 ... 2 давайте разбираться.
Какой коэффициент деления предделителя установить?
Собственно говоря, разбираться тут особо и нечего: "привязываемся" к байту, что означает коэффициент деления 256.
Теоретически, можно "привязаться" и к полубайту (128) и к четверти байта (64), но, при этом, наживаются проблемы, связанные с необходимостью прямо или косвенно производить операции либо с полубайтами, либо с четвертями байта, со всеми вытекающими неприятными последствиями, что вовсе не желательно.
Итак, в битах №№ 0 ... 2 выставляем единицы.
Смотрим, что получилось.
Теперь переполнения TMR0 будут происходить не через 256 тактов (импульсов), а через 256*256=65536 тактов.
Таким образом, руководствуясь принципом "недопущения перебора", за время "прохождения" 3 000 000 импульсов (напоминаю, время измерения составляет не 1 сек., а 0,1 сек.) произойдет 45 переполнений TMR0.
65536*45=2949120. Остаток 3000000-2949120=50880.
Обратите внимание: число 45 не выходит за пределы числового диапазона байта (не более 255) и оно "уместится" в одном байте.
Таким образом, речь идет о том, что (немного забегаю вперед, ну да ладно), используя описываемый принцип счета (применительно к данному, конкретному случаю), можно измерить частоты более высокие, чем 30 мГц. (можете сами посчитать).
Просто 30 мГц. это "потолок" PIC16F84A, а если будет использован более быстродействующий ПИК, то можно считать импульсы и с более высокой частотой.
Вернемся к математике.
Имеется остаток равный 50880.
На 65536 его делить нельзя, так как получится число меньше единицы, значит делим на 256.
С учетом соблюдения все того же принципа "недопущения перебора", получится следующее:
256*198=50688. Остаток 50880-50688=192.
Еще раз обратите внимание, что и число 198, и число 192 не выходят за пределы числового диапазона байта и каждое из них можно отобразить в одном байте.
Итожим:
3 000 000=256*256*45+256*198+192
Вы видите перед собой математическую форму разложения десятичного числа 3000000.
Для того чтобы убедиться, что между этой формой представления числа (используемые числа - десятичные) и бинарной формой представления этого же числа имеется однозначное соответствие, в программке конвертора систем исчислений, имеющейся у Вас, "настучите" число 3000000.
Вы увидите, что десятичное число 3000000 в бинарном виде выглядит так:
00101101 11000110 11000000
 А теперь по частям:
- старший байт: 256*256*45=2949120 (00101101 00000000 00000000), (00101101=.45),
- средний байт: 256*198=50688 (00000000 11000110 00000000), (11000110=.198),
- младший байт: 192 (00000000 00000000 11000000), (11000000=.192).
Остается только "закрепить" за старшим байтом (число .45) - регистр TimerH, за средним байтом (число .198) - регистр TimerM, за младшим байтом (число .192) - регистр TimerL.
Каждое из этих трех чисел отображается в одном байте.
При соответствующем распределении этих 3-х байтов (в порядке старшинства), 3-байтное двоичное число, записанное в таком 3-байтном регистре (TimerH/TimerM/TimerL), будет соответствовать 7-разрядному десятичному числу 3 000 000.
Переполнение TMR0 будет происходить 3000000/65536=45,776367 раз в 0,1 сек. или 457,76367 раз за 1 сек., что соответствует времени счета TMR0, от момента перехода от .255 к .00 и до следующего момента перехода от .255 к .00, равному 2184,5 мкс. (2,1845 мс.).
Это время достаточно велико, особенно с учетом того, что речь идет об измерении относительно высокой частоты (30 мГц.) и прекрасно "вписывается" в малое кольцо динамической индикации (напоминаю, что процессы счета и динамической индикации совмещаются).
В данном случае, полный цикл малого кольца динамической индикации не должен превышать 2184,5*2=4369 мкс., в противном случае, возникнут сбои за счет того, что 2 переполнения TMR0 будут считаться за одно.
Примечание: счет прекращается практически сразу же после выхода рабочей точки программы из малого кольца динамической индикации и возобновляется непосредственно перед ее входом в малое кольцо динамической индикации (есть небольшой временной "зазор", в течение которого счет происходит, но он настолько мал, что его можно не принимать в расчет).
По этой причине, при условии соблюдения сформулированного выше правила, можно не опасаться двойного переполнения TMR0 на первом "витке" малого кольца индикации.
Если указанное выше превышение не велико, то такие сбои будут наблюдаться на частотах, близких к максимальной частоте счета, а на более низких частотах они наблюдаться не будут.
По мере его увеличения, сбои будут наблюдаться и на более низких частотах, то есть, "зараза" движется по оси частот сверху вниз и ширина зоны ее действия зависит от степени превышения полного цикла малого кольца динамической индикации над удвоенным интервалом времени между двумя соседними переполнениями TMR0.
Еще один вывод: для того чтобы отодвинуть верхнюю границу полосы частот, в пределах которой производится счет, вверх по оси частот, необходимо уменьшить время полного цикла малого кольца динамической индикации.
Естественно, в этом случае, нужно не забывать о том, что это влияет на время полного цикла динамической индикации и на величину измерительного интервала времени.
Итак, команды 1-й проверки TMR0 на переполнение "встраиваются" в малое кольцо динамической индикации, а результат этих подсчетов "оседает" в регистре TimerH.
Вопрос: Каким образом происходит подсчет?
Ответ: каждый раз, при наличии факта переполнения TMR0 (флаг T0IF поднят), содержимое регистра TimerH инкрементируется.
Количество таких инкрементов напрямую зависит от частоты импульсной последовательности, подаваемой на вывод RA4/TOCKI (на вход предделителя).
Если речь идет о 1-й проверке TMR0 на переполнение, то, "по закону жанра" должна быть и вторая?
Так оно и есть.
Дело в том, что, после 1-й проверки TMR0 на переполнение и до момента окончания счета (блокировки вывода RA4/TOCKI) располагается группа команд точной "доводки" измерительного интервала времени и группа команд завершения измерения, за время исполнения которых может произойти переполнение TMR0.
Следовательно, сразу же после окончания счета необходимо произвести еще одну проверку на переполнение TMR0 (№ 2).
Если, за указанный выше промежуток времени, произошло переполнение TMR0 (из-за малой его величины, количество переполнений не может быть больше одного), то содержимое регистра TimerH инкрементируется, а если не произошло, то содержимое регистра TimerH не меняется.
Итак, с регистром старшего разряда TimerH мы, в основном, разобрались.
Будем считать, что старший байт числа 01101101 00000000 00000000 (2949120) в регистр TimerH записан, а это означает, что старший байт 3-байтного двоичного числа результата подсчета сформирован.
Итог по регистру TimerH:
В ходе исполнения процедур проверок переполнения TMR0 с последующими инкрементами содержимого регистра TimerH, числовое значение байта регистра TimerH будет увеличиваться.
Интенсивность этого увеличения зависит от значения частоты последовательности импульсов, подаваемых на счетный вход ПИКа от внешнего источника импульсов.
После разрешения счета, на каждом "витке" малого кольца динамической индикации, происходит 1-я проверка переполнения TMR0, за счет которой и осуществляется основной "прирост" числового значения байта регистра TimerH.
После того, как счет запрещается, происходит 2-я проверка переполнения TMR0, которая может либо привести, либо не привести к единственному инкременту содержимого регистра TimerH.
После 2-й проверки переполнения TMR0, в регистре TimerH будет окончательно сформировано числовое значение старшего байта 3-байтного двоичного числа результата измерения.
В рассматриваемом случае, это число 0010 1101 (.45).
Для того чтобы увеличить значение десятичного эквивалента этого 3-байтного двоичного числа до 3000000, необходимо "что-то" записать в регистр среднего разряда TimerM и в регистр младшего разряда TimerL.
Начнем с регистра среднего разряда TimerM.
Итак, счет закончен и в регистре TimerH записано количество переполнений TMR0.
Вопрос: что нужно записать в регистр среднего разряда TimerM?
Ответ: в него нужно записать содержимое TMR0 на момент окончания второй проверки на переполнение TMR0.
Эта операция простая: содержимое TMR0, через регистр W, пересылается (копируется) в регистр TimerM.
Таким образом, в рассматриваемом случае, в средний байт 3-байтного двоичного числа (TimerM) записывается число 1100 0110 (.198).
Переходим к регистру младшего разряда TimerL.
Вопрос "на засыпку": если количество переполнений TMR0 записалось в регистр TimerH, а содержимое TMR0 скопировалось в регистр TimerM, то что же тогда записывать в регистр TimerL?
Ответ: если не найти "место" в котором "лежит" "хвостик" разложения значения подсчитываемого количества импульсов (в нашем случае - число 192), то записывать в регистр TimerL нечего.
И в самом деле, TMR0 "выжат как мочалка" и "выжимать" из него нечего, но из предделителя кое-что "выжать" можно.
Забегая вперед, скажу, что именно в байте предделителя и "лежит" этот "хвостик".
А вот здесь начинается самое интересное в том смысле, что то, что будет описано ниже, для многих программистов, и даже для тех, кто имеет определенный опыт, является "темной лошадкой".
Речь идет о такой "головоломке", как подпрограмма досчета.
Из самого ее названия понятно, что организуется дополнительная процедура досчета.
Именно результат работы этой ПП и будет записываться в регистр TimerL.
Давайте разберемся что это такое сначала в принципе, а затем (при разборе "программы") и более детально.
Принцип работы подпрограммы досчета
 Перед исполнением ПП досчета, содержимое регистра TimerL необходимо сбросить в ноль (подготовка к досчету).
Далее, необходимо организовать счет (ведь досчет есть тот же самый счет, только от числа не обязательно равного нулю и до "верхней" числовой границы байта), то есть, сформировать короткий счетный импульс.
Это делается путем кратковременной установки на выводе блокировки RA3 единичного уровня.
Иными словами, программными средствами, формируется короткий счетный импульс.
В нашем случае (приращение TMR0 по переднему фронту), в момент начала формирования короткого счетного импульса, формируется активный перепад (переход от 0 к 1), поступающий на счетный вход ПИКа (на вход предделителя), который и будет подсчитан.
Возникает вопрос: а не будут ли, во время формирования единичного уровня счетного импульса, подсчитываться импульсы от внешнего источника, ведь этого допустить нельзя, так как основной интервал времени счета закончился, а "лишние" импульсы от внешнего источника, подсчитываемые вне этого интервала "исказят" результат подсчета (в сторону увеличения)?
Ответ: импульсы от внешнего источника подсчитываться не будут, так как, на момент исполнения ПП досчета, блокировочный вывод RA3 настроен на работу "на выход", что соответствует подключению к счетному входу ПИКа низкоомного выхода защелки № 3 порта А.
В этом случае, на счетном входе ПИКа, амплитуда импульсов, поступающих от внешнего источника, будет резко снижена (она окажется ниже порога срабатывания счетного входа ПИКа) за счет шунтирования счетного входа ПИКа низким выходным сопротивлением защелки № 3 порта А, из-за чего счетный вход ПИКа перестанет "реагировать" на импульсы от внешнего источника вне зависимости от того, установился ли на выходе этой защелки 0 или 1.
С другой стороны, короткие счетные импульсы, формируемые ПП досчета, подсчитываться будут по той простой причине, что они формируются на выходе защелки № 3 порта А, который (выход) электрически подключен к счетному входу ПИКа (выводы RA3 и RA4/TOCKI электрически соединены между собой - см. схему ЧМ/ЦШ).
После формирования короткого счетного импульса, может наступить 2 события: "содержимое TMR0 не инкрементировалось" и "содержимое TMR0 инкрементировалось".
До сих пор я не обращал Вашего внимания на байт предделителя.
Пришла пора с ним разобраться.
Байт предделителя, по отношению к байту TMR0, является младшим, и этим все сказано.
И в самом деле, где как не в байте предделителя находиться "хвостику" основного счета, который и нужно записать в регистр TimerL для получения, в рассматриваемом случае, десятичного эквивалента 3-байтного двоичного числа, точно равного 3 000 000?
В рассматриваемом случае, в байте предделителя "лежит" число .192 (см. выше) и задача сводится к "переправке" этого числа из байта предделителя в байт регистра TimerL.
Так как предделитель не является регистром, отображаемым в области оперативной памяти, то "переправить" его содержимое в регистр TimerL, при помощи стандартной процедуры записи через регистр W, нельзя.
Самое разумное, что можно сделать, это инкрементировать содержимое байта предделителя до тех пор, пока не произойдет переход от .255 к .00.
В этом случае, произойдет инкремент содержимого регистра TMR0, что и будет являться критерием конца досчета.
Факт инкремента содержимого регистра TMR0 укажет на то, что байт предделителя инкрементирован количество раз, равное разнице между числом 256 и числом, находящимся в байте предделителя на момент окончания основного счета (числом, которое нужно "переправить" в регистр TimerL).
Подсчитав количество инкрементов байта предделителя с момента начала подпрограммы досчета и до момента инкремента байта TMR0, можно, после преобразования числа количества этих инкрементов в другую форму, точно восстановить числовое значение байта предделителя на момент окончания основного счета ("хвостик") с записью этого значения в регистр TimerL (что и требуется).
Обращаю Ваше внимание, на то, что речь идет именно о досчете, а не об основном счете.
Разница между ними заключается в том, что основной счет производится во время формирования измерительного интервала времени, на момент окончания которого, в байте предделителя фиксируется некое число из диапазона .00 ... .255 (зависит от частоты импульсной последовательности, подаваемой на счетный вход ПИКа), а досчет производится от этого "некоего" числа и до момента увеличения на 1 содержимого TMR0 (до момента смены числового значения байта предделителя с .255 на .00) с целью записи зафиксированного (на момент окончания основного счета) в байте предделителя числа, в байт регистра TimerL.
Таким образом, значение результата досчета не есть значение результата основного счета, но значение результата досчета, тем не менее "жестко привязано" к значению результата основного счета и, после соответствующего числового преобразования, по значению результата досчета можно точно восстановить значение результата основного счета.
Такого рода высказывание, поначалу, чем-то напоминает "китайскую грамоту" и, для того чтобы наступило "просветление", для начала, нужно разобраться, каким же образом происходит восстановление значения результата основного счета (зафиксированного в байте предделителя на момент окончания основного счета) по значению результата досчета.
Начнем с "голой" математики, применительно к рассматриваемому случаю (подсчет 3 000 000 импульсов за 0,1 сек).
Итак, байт предделителя отображает число .192.
Задача: "переправить" число .192 из байта предделителя в байт регистра TimerL при помощи процедуры досчета, с последующим числовым преобразованием.
Математически это выглядит так:
1. 256-192=64 (значение результата досчета).
2. 64+1=65 (инкремент).
3. Переводим десятичное число .65 в бинарную форму: 0100 0001.
4. Инвертируем все биты числа 0100 0001 (.65). Получаем: 1011 1110, что соответствует десятичному числу 190.
5. 190+1=191 (инкремент).
6. 191+1=192 (инкремент).
Итог: число 192 "переправлено" из байта предделителя в байт регистра TimerL.
Для того чтобы Вы убедились, что, при подсчетах импульсов импульсных последовательностей с другими частотами, происходит то же самое, приведу еще один пример.
Например, считается 1 500 000 импульсов за 0,1 сек.
Делаем разложение:
256*256*22+256*227+96
 1. 256-96=160 (значение результата досчета).
2. 160+1=161 (инкремент).
3. Переводим десятичное число .161 в бинарную форму: 1010 0001.
4. Инвертируем все биты числа 1010 0001 (.161). Получаем: 0101 1110, что соответствует десятичному числу 94.
5. 94+1=95 (инкремент).
6. 95+1=96 (инкремент).
Как видите, алгоритм процедуры "переправки" содержимого байта предделителя в байт регистра общего назначения (в нашем случае - с названием TimerL) в обоих случаях одинаков, что свидетельствует о том, что этот алгоритм будет безошибочно работать при любой скорости счета.
Можете сами убедиться в этом, проделав описанные выше математические операции для других скростей счета.
Перед тем, как произвести детальный анализ конструкции групп команд счета, подведу краткий общий итог:
Наибольший практический интерес представляет собой принцип организации высокоскоростного счета.
При этом, перед TMR0 включается предделитель с коэффициентом деления 256, на вход которого (предделителя), с выхода внешнего источника импульсов, подается последовательность импульсов, количество которых нужно посчитать за интервал времени основного счета.
Организуется 3-байтный (в рассматриваемом случае) регистр, в старший разряд которого записывается количество переполнений TMR0, произошедших с начала разрешения основного счета и до конца 2-й проверки на переполнение TMR0, в средний разряд записывается число, зафиксировавшееся в TMR0 на момент окончания основного счета, в младший разряд записывается число, зафиксировавшееся в байте предделителя на момент окончания основного счета.
Для детального разбирательства с механизмом высокоскоростного счета используется файл Tmr0.asm.

Файл "программы" Tmr0.asm Вы можете скачать: здесь.

Механизм низкоскоростного счета я разбирать не буду: он проще механизма высокоскоростного счета (в дальнейшем - просто "счета"), и если Вы освоите последнее, то с первым не будет никаких проблем.
Группа команд счета начинается с команд подготовительных операций, сразу же после которых следуют команды начала счета.
Перечисленные выше команды достаточно последовательно исполнить по одному разу (каждую) за один полный цикл программы, и, по этой причине, они расположены на "линейном" участке программы до входа в малое и большое кольцо динамической индикации (до 1-й команды группы ПП динамической индикации), что Вы и видите в тексте "программы" Tmr0.asm.
Напоминаю, что входы в малое и большое кольцо динамической индикации совмещены.
В соответствии со сказанным ранее, группа команд подготовительных операций должна включать в себя команду запрета прерываний (clrf IntCon), команду сброса в 0 содержимого регистра TMR0 (clrf Tmr0), команду сброса в 0 содержимого регистра TimerH (clrf TimerH) и 2 команды закладки константы (через регистр W) в регистр специального назначения OPTION.
Для удобства восприятия константы, она представлена в бинарном виде.
Регистр OPTION "лежит" в 1-м банке, следовательно, перед работой с ним, нужно перейти в 1-й банк (bsf Status,RP0). Используя распечатку регистра OPTION, Вы можете убедиться в том, что, после исполнения команды movwf Option, вывод RA4/TOCKI подключится к входу предделителя, выход предделителя подключится к входу TMR0, коэффициент деления предделителя будет равен 256 и приращение содержимого TMR0 будет происходить в момент смены уровня внешнего тактового сигнала с 0 на 1 (по переднему фронту).
Примечание: есть 2 способа обозначения перепадов импульса.
Если смены 0 на 1 и 1 на 0 называть передним и задним фронтом импульса соответственно, то смена 0 на 1 будет называться "передним фронтом" импульса, а смена 1 на 0 - "задним фронтом" импульса.
Если смены 0 на 1 и 1 на 0 называть фронтом и спадом импульса соответственно, то смена 0 на 1 будет называться "фронтом" импульса, а смена 1 на 0 - "спадом" импульса.
После исполнения команд подготовительных операций, необходимо сформировать интервал времени счета.
Для этого необходимо, чтобы вывод RA4/TOCKI (счетный вход ПИКа или вход предделителя) работал "на вход" (установить в бите № 4 регистра TrisA единицу) и вывод блокировки RA3 также работал "на вход" (в этом случае, сопротивление между выводом RA3 и корпусом велико и его шунтирующим действием можно принебречь).
Регистр TrisA "лежит" также в 1-м банке, поэтому банк менять не нужно (ранее установлен 1-й банк).
Константа b'00011000', через регистр W, "переправляется" в регистр TrisA, после чего выводы RA3 и RA4/TOCKI настраиваются на работу "на вход", а все остальные выводы порта А - на работу "на выход".
В конце группы команд начала счета необходимо "вернуться" в нулевой банк (bcf Status,RP0), так как далее будут производиться операции с регистрами нулевого банка.
Убедитесь, что упомянутая выше последовательность действий в тексте "программы" Tmr0.asm соблюдается.
Счет разрешается после того, как константа будет записана в регистр TrisA, то есть, после исполнения команды movwf TrisA.
В тексте "программы", для удобства, я пометил эту команду меткой On (команда начала основного счета).
Если она (метка) не нужна, то ее можно убрать, так как в тексте "программы" обращений к ней нет (то же самое касается и метки Off, которой помечена команда конца основного счета).
Примечание: установка в тексте программы (любой) метки (меток), к которой нет обращения, не является ошибкой, и работа программы от этого не нарушится.
Ошибкой является обращение к метке, которой нет (которая не установлена).
Итак, перед входом рабочей точки программы в группу ПП динамической индикации, произведены подготовительные операции и разрешен счет.
В соответствии со сказанным выше, далее, должна быть произведена первая проверка на переполнение TMR0.
Она должна быть "врезана" в малое кольцо динамической индикации, так как, в этом случае, обеспечивается периодичность проверок, гарантирующая безошибочный подсчет в пределах старшего байта 3-байтного двоичного числа, формируемого по результатам счета, в 3-байтном регистре TimerH/TimerM/TimerL (почему? См. выше).
Первая проверка на переполнение TMR0 начинается с опроса состояния флага прерывания по переполнению TMR0 (btfss IntCon,2), поднятие которого не зависит от того, разрешены прерывания или нет, а зависит только от факта переполнения TMR0.
Ранее, в подготовительных операциях, все биты регистра IntCon были сброшены в 0, следовательно, на момент разрешения счета, флаг прерывания по переполнению TMR0 (T0IF) будет также сброшен в 0, что и требуется в начале процедуры опроса.
Напоминаю: флаг T0IF относится ко 2-й группе флагов и, если он поднялся, то в 0 его нужно сбрасывать программно.
Команда ветвления btfss IntCon,2 разветвляет программу на 2 сценария, которые, ниже по тексту "программы", снова сходятся на первой команде некой ПП с условным названием О_К (или на команде, помеченной метой О_К, если это метка).
При таком разветвлениии на 2 сценария с последующим их схождением, необходимо принять меры к выравниванию времени исполнения сценария с меньшим временем исполнения по времени исполнения сценария с большим (ударение на о) временем исполнения (о такой необходимости говорилось ранее).
В рассматриваемом случае, оба этих сценария исполняются в пределах малого кольца динамической индикации, и время полного цикла этого кольца определяет величину калиброванного интервала времени основного счета.
Таким образом, для устранения нестабильности счета, необходимо оба этих сценария уровнять по времени выравнивающими NOPами, что Вы и видите в тексте "программы" (почему их три? См. ниже).
Разбираем сценарии.
1-й сценарий.
Если произошло переполнение TMR0 (флаг T0IF поднялся), команда goto DoNothing не исполняется (вместо нее исполняется команда "виртуального" NOPа), и рабочая точка программы "встает" на команду incf TimerH,F.
После исполнения этой команды происходит инкремент содержимого регистра TimerH с сохранением результата инремента в нем же.
Так как, при отработке группы ПП динамической индикации, формируется большое количество малых колец динамической индикации, то будет происходить и такое же большое количество проверок на переполнение TMR0.
Во всех случаях этих проверок, когда флаг T0IF поднимается, будет происходить инкремент содержимого регистра TimerH.
Таким образом, к моменту выхода рабочей точки программы из большого кольца динамической индикации, в регистре TimerH "осядет" число, равное количеству переполнений TMR0, произошедших с момента разрешения основного счета и до момента выхода рабочей точки программы из большого кольца динамической индикации, находящееся в прямо пропорциональной зависимости от частоты импульсной последовательности, подаваемой на счетный вход ПИКа.
После инкремента содержимого регистра TimerH, бит флага T0IF необходимо программно сбросить в 0, что и делается при помощи команды bcf IntCon,2.
После этого происходит безусловный переход в ПП О_К (или на метку О_К, если это метка).
2-й сценарий.
Если переполнения TMR0 не произошло (флаг T0IF не поднялся), то команда инкремента содержимого регистра TimerH не выполняется, а происходит безусловный переход в ПП "выравнивания" с названием DoNothing (goto DoNothing).
О необходимости наличия группы команд "выравнивающих" NOPов было сказано выше.
Эту группу команд можно считать подпрограммой DoNothing, но название этой ПП можно считать и меткой, выставленной на первом NOPе этой группы команд - это кому как нравится.
Почему именно три NOPа?
Давайте разбираться.
Если считать точкой "разветвления" на 2 сценария команду ветвления btfss IntCon,2, а точкой "слияния" этих 2-х сценариев - первую команду ПП О_К (а это так и есть), то исполнение самого короткого из этих двух сценариев (btfss - 1 м.ц. / goto - 2 м.ц.) займет 3 м.ц. (предполагаем, что ПП DoNothing нет), а исполнение самого длинного (btfss - 1 м.ц. / "виртуальный" NOP - 1 м.ц. / incf - 1 м.ц. / bcf - 1 м.ц. / goto - 2 м.ц.) - 6 м.ц..
6-3=3, следовательно, необходимо 3 "выравнивающих" NOPа, что Вы и видите в тексте "программы".
После группы команд 1-й проверки на переполнение TMR0, следуют остальные команды группы ПП динамической индикации и группа команд точной "доводки" интервала времени основного счета до расчетного значения.
Таким образом, группа команд 1-й проверки на переполнение TMR0 является "врезкой" в малое кольцо динамической индикации, время исполнения которой нужно учитывать при формировании полного цикла этого кольца.
После прохождения рабочей точкой программы нижней границы большого кольца динамической индикации (после этого происходит выход рабочей точки программы из группы ПП динамической индикации) и исполнения группы команд точной "доводки" интервала вемени основного счета до расчетного значения, необходимо завершить основной счет.
Перед тем, как это сделать, необходимо установить на выходе защелки № 3 нулевой (блокирующий) уровень.
В данном случае, в 0 сбрасывается не бит № 3 регистра PortA (можно сделать и так: bcf PortA,3), а весь байт, причем, для разнообразия, эта операция организована не с использованием команд movlw 0 и movwf PortA (можно сделать и так), а с использованием команды сброса в 0 содержимого регистра W (clrw).
Посмотрите как это сделано в тексте "программы" (clrw и movwf PortA).
Примечание: установка на выходе защелки № 3 нулевого уровня не есть окончание счета, так как, пока, этот выход не подключен к выводу RA3 (он работает "на вход" - см. выше).
Это подключение произойдет позднее, когда этот вывод переключится с работы "на вход" на работу "на выход" (см. ниже - метка Off).
Принцип организации завершения счета такой же, как и принцип организации его начала.
Разница заключается только в значении константы (низкое выходное сопротивление защелки с № 3 блокирует счетный вход ПИКа, в результате чего основной счет прекращается).
Команда конца основного счета помечена меткой Off.
Так как в процессе выхода рабочей точки программы из группы ПП динамической индикации (на последнем "витке" большого кольца динамической индикации) и дальнейшего исполнения группы команд точной "доводки" интервала времени основного счета до расчетного значения, может произойти переполнение TMR0, то необходимо организовать вторую проверку на переполнение TMR0.
Принцип ее организации такой же, как и первой проверки, а отличие заключается в том, что, в этом случае, необходимость в "выравнивании" сценариев отпадает, так как вторая проверка производится вне интервала времени основного счета (основной счет закончен), и, по этой причине, безусловный переход осуществляется не в ПП "выравнивания", а в ПП формирования содержимого регистра TimerM с названием Analyse.
Не трудно заметить, что, в ходе 2-й проверки на переполнение TMR0, количество инкрементов не может быть больше одного, так как, после исполнения операции первого же инкремента, следующая проверка на переполнение TMR0 (№ 1) может быть произведена только на следующем "витке" полного цикла "программы".
После окончания этой проверки, содержимое регистра TimerH будет окончательно сформировано и можно перейти к формированию содержимого регистра TimerM.
В этом случае, все гораздо проще: всего-навсего нужно скопировать (через регистр W) содержимое регистра TMR0 в регистр TimerM (movf Tmr0,W и movwf TimerM).
После этого, содержимое регистра TimerM будет окончательно сформировано и можно перейти к формированию содержимого регистра TimerL.
Вопрос: "Почему, перед проведением операции с содержимым регистра TimerM, его содержимое, предварительно, не было сброшено в 0, как, например, перед проведением операций с содержимым регистра TimerH"?
Ответ: А зачем? Ведь речь идет об "одноразовой" записи новых данных "поверх" старых, при которой величина числа, находящегося в регистре перед записью, не имеет значения.
Что же касается содержимого регистра TimerL на момент начала ПП досчета CountIt, то его, перед началом этой ПП, обязательно нужно сбросить в 0, так как далее будут производиться операции с "привязкой" к начальной точке отсчета, которой является 0, что и сделано (clrf TimerL).
Работа подпрограммы досчета.
 Напоминаю, что смысл работы этой ПП (CountIt) заключается в "выталкивании" числа, зафиксировавшигося в байте предделителя на момент окончания основного счета, в регистр TimerL.
Таким образом, ПП досчета является специфической формой копирования этого числа в регистр TimerL в том смысле, что это копирование происходит не напрямую, а косвенно, то есть, посредством организации "принудительной" процедуры досчета с последующим преобразованием результата досчета.
После окончания процедуры досчета, ее результат будет "находиться" в регистре TimerL, после чего производится процедура восстановления числового значения байта предделителя на момент окончания основного счета, по результату досчета.
По окончании процедуры восстановления, в регистр TimerL будет записан результат этой процедуры в виде числа, в точности равного числу, зафиксировавшемуся в байте предделителя на момент окончания основного счета.
ПП досчета CountIt начинается с инкремента содержимого регистра TimerL, с сохранением результата инкремента в нем же (incf TimerL,F).
Далее, путем смены уровней на выходе защелки № 3 порта А, формируется короткий счетный импульс.
Ничего сложного в процессе его формирования нет (см. комментарии в тексте "программы").
По переднему фронту этого импульса происходит увеличение на 1 (инкремент) значения байта предделителя.
Далее, организуется проверка числового значения байта регистра TMR0 с целью ответа на вопрос: "Изменилось (увеличилось на 1) или нет числовое значение байта регистра TMR0?"
Естественно, что для подобного рода проверки, необходимо произвести сравнение числа, заложенного в регистр TMR0 до прохождения счетного импульса, с числом, заложенным в регистр TMR0 после прохождения счетного импульса.
Ранее, число, "осевшее" в регистре TMR0 на момент окончания основного счета, было скопировано из регистра TMR0 в регистр TimerM (см. ПП Analyse), следовательно, после прохождения счетного импульса, необходимо произвести сравнение содержимого регистров TimerM и TMR0 на предмет "одинаковости" или нет чисел, записанных в них на момент сравнения чисел.
Числовое значение байта регистра TimerM (эталон) не может измениться, так как счетный импульс не может воздействовать на его содержимое, а вот числовое значение байта регистра TMR0, при смене числового значения байта предделителя с .255 на .00, будет инкрементировано (изменится).
Во всех остальных случаях (смены числового значения байта предделителя с .255 на .00 нет), инкремента содержимого регистра TMR0 производится не будет (будут производиться инкременты содержимого байта предделителя), и рабочая точка программы будет "мотать кольца" в ПП досчета CountIt.
На каждом "витке" этого "кольца", содержимое регистра TimerL будет инкрементироваться, и эти инкременты будут происходить до тех пор, пока числовое значение байта предделителя не сменится с .255 на .00
В момент этой смены, происходит инкремент содержимого регистра TMR0, что приведет к тому, что числовое значение байта регистра TMR0 перестанет быть равным эталону (числовому значению байта регистра TimerM), после чего сценарий "мотания колец" прекращается, а в регистре TimerL "оседает" результат досчета в виде 8-битного двоичного числа, значение которого равно разнице между числом 256 и числовым значением байта предделителя на момент начала процедуры досчета (конца основного счета).
После этого, рабочая точка программы начинает "двигаться" по сценарию "программа исполняется далее" и "заходит" в группу команд преобразования формы числа результата досчета, после исполнения которых, числовое значение байта регистра TimerL будет в точности равно числовому значению байта предделителя на момент начала процедуры досчета (конца основного счета), что и требуется.
"Мотание колец" происходит в группе команд, реализующих циклическую ПП задержки.
Эту же группу команд можно назвать группой команд, реализующих однобайтный суммирующий счетчик с предустановкой, что и определяет наличие такого термина, как "досчет".
Предустановка заключается в том, что, на момент начала счета, в байт такого счетчика ранее записано число (младший байт результата основного счета), и счет начинается не от 0, а от этого числа.
Этот счетчик, как бы, переведен на режим "самообслуживания" в том смысле, что тактовый сигнал формируется программными средствами, а внешний тактовый сигнал не используется.
По этой причине, непосредственно, к процессу основного счета, процесс досчета никакого отношения не имеет, но он "жестко привязан" к результату счета, так как процесс досчета начинается от числового значения младшего байта результата основного счета.
По этой причине, в ходе процедуры досчета, в результат основного счета погрешностей не вносится (естественно, при условии отсутствия в ПП досчета ошибок), и процедура досчета является чисто технической процедурой "переправки" числа из байта предделителя в байт регистра TimerL.
Вернемся к тексту ПП досчета.
После формирования счетного импульса, исполняется команда movf Tmr0,W.
После ее исполнения, содержимое регистра TMR0 копируется в регистр W. Это необходимо для проведения операции сравнения числа, записанного в регистре TMR0, с эталоном (см. выше).
Для обеспечения комфортности отладки "программы", флаг нулевого результата Z сбрасывается в 0 (bcf Status,Z).
Далее, при помощи команды subwf TimerM,W, производится сравнение чисел, записанных в байтах регистров W и TimerM.
Сравнение производится путем вычитания содержимого регистра W из содержимого регистра TimerM.
В регистре TimerM находится эталон, то есть, число, зафиксировавшееся в TMR0 после окончания основного счета, а в регистре W может находиться либо то же самое эталонное число (числовое значение байта предделителя не изменилось с .255 на .00), либо число отличное от эталона (числовое значение байта предделителя изменилось с .255 на .00).
Если, после команды subwf TimerM,W (команда subwf воздействует на флаг Z - см. распечатку команд), произвести проверку бита флага нулевого результата Z (btfsc Status,Z), что в тексте ПП CountIt и сделано, то, в первом случае, будет исполнен сценарий "намотки колец", а во втором случае - сценарий "программа исполняется далее", .
В сценарии "намотки колец", по команде goto CountIt, происходит переход рабочей точки программы на начало исполнения ПП досчета, после чего ПП досчета исполняется снова и содержимое регистра TimerL еще раз инкрементируется и т.д.
Такие кольца будут "наматываться" до тех пор, пока числовое значение байта предделителя не изменится с .255 на .00, что соответствует инкременту содержимого TMR0.
На этот момент времени, содержимое регистра TimerL будет инкрементировано количество раз, равное разнице между числом 256 и числом, зафиксировавшимся в байте предделителя после окончания основного счета, что и есть результат досчета. После инкремента содержимого TMR0, результат исполнения команды subwf TimerM,W, станет не равным нулю, и произойдет переход на сценарий "программа исполняется далее".
Таким образом, рабочая точка программы "заходит" в группу команд преобразования формы числа.
Далее все очень просто: вспомните, как, занимаясь математикой, мы "химичили" с числом 192 (см. выше), и сразу становится понятно, что последовательность команд группы команд преобразования формы числа должна быть следующей:
1. Инкремент содержимого регистра TimerL с сохранением результата инкремента в нем же (incf TimerL,F).
2. Инверсия всех битов содержимого регистра TimerL с сохранением результата инверсии в нем же (comf TimerL,F).
3. и 4. Два последовательных инкремента содержимого регистра TimerL с сохранением результата инкремента в нем же (две команды incf TimerL,F).
"Врезка" групп команд высокоскоростного счета в тексты программ + общий итог
 Вопрос: "Можно ли обойтись без второй проверки на переполнение TMR0"?
Ответ: можно, если сразу же после первой проверки на переполнение TMR0 основной счет прекратить.
Если организуются 2 проверки на переполнение TMR0, то время прохождения рабочей точкой программы участка программы между этими проверками не должно быть большим, и оно не должно превышать времени двойного переполнения TMR0 на самой высокой расчетной скорости счета, иначе, 2 переполнения посчитаются как одно.
При выборе частоты проверок на переполнение TMR0 № 1, следует учитывать, что, чем чаще они следуют, тем большую скорость счета можно достигнуть, но и слишком частыми их делать не стоит, так как значительное превышение значения этой расчетной скорости счета над предельно допустимой максимальной скоростью счета ПИКа, в практическом отношении, ничего не даст (выше того, на что способен счетный вход ПИКа не "прыгнешь").
Процесс счета происходит в границах калиброванного интервала времени основного счета и, естественно, именно в этих границах, и должны быть организованы проверки на переполнение TMR0.
А это, в свою очередь, предполагает наличие, в границах калиброванного интервала времени основного счета, циклической подпрограммы, в которую и нужно "врезать" команды первой проверки на переполнение TMR0.
Количество "колец", "наматываемых" рабочей точкой программы в этой циклической подпрограмме, в промежутке времени между первым заходом рабочей точки программы в нее и окончанием времени основного счета, и есть количество проверок на переполнение TMR0 за время калиброванного интервала времени основного счета.
Естественно, что, если речь идет о такого рода "заходе" и, самое главное, о "выходе", то это означает необходимость наличия счетчика циклов (по образу и подобию счетчика больших колец индикации в группе ПП динамической индикации).
В конечном итоге, отладка механизма высокоскоростного счета, применительно к разрабатываемому устройству, сводится к "помещению" фактического количества проверок на переполнение TMR0 в "разрешенную зону" количества проверок, которая достаточно широка, а уж какое конкретное место в этой "зоне" выберет программист, это, как говорится, его личное дело (критерии такого выбора были сформулированы ранее).
Что касается остальных команд процедуры счета, то, в большинстве случаев, они следуют "плотной группой" сразу же после последней команды 2-й проверки на переполнение TMR0 (или после команд конца счета, если 2-й проверки производить не нужно), и как-то менять такой порядок не имеет практического смысла (хотя и можно).
В конечном итоге, если разобраться, то "встраивание" групп команд счета в текст той или иной программы вовсе не является каким-то уж очень "заумным" делом, и если знать "откуда ноги растут", то и проблем не будет.
В следующем разделе Вы увидите, как группы команд счета "врезаются" в текст конкретной, "полноценной", работающей программы.
Переходите к следующему разделу.


Kea_38

18. Пример программы, включающей в себя группы подпрограмм 15, 16, 17 разделов. Задания на разработку.

А теперь сведем все то, о чем говорилось в 15, 16 и 17 разделах (и ранее) в единое целое, то есть в самую что ни на есть "полноценную", рабочую программу, которая обеспечивает функционирование 7-разрядного частотомера и которую можно "зашить" в ПИК.
Эта программа называется Kea.asm, и она представляет собой "чистый" частотомер (без функции цифровой шкалы, клавиатура отсутствует), "получившийся" из ЧМ/ЦШ А.Денисова после того, как я "убрал из него все лишнее".
Хотя эта программа и является обучающей, но на ее основе вполне можно "соорудить" простенький 7-разрядный частотомер, который будет работать даже лучше, чем частотомер ЧМ/ЦШ А.Денисова и мой, в том смысле, что мерцания будут ощущаться меньше.
И это вполне закономерно, так как, в предлагаемом Вашему вниманию 7-разрядном частотомере, соотношение времени полного цикла программы к времени полного цикла динамической индикации лучше, чем в более "навороченных" частотомерах, за счет значительного уменьшения количества операций, производимых вне полного цикла динамической индикации, ухудшающих это соотношение.
Если у кого-то имеется желание сделать такой частотомер, то, проассемблировав текст программы Kea.asm в MPLAB, Вы получите HEX-файл, который можно открыть в программе, обслуживающей Ваш программатор.
Схема этого частотомера такая же, как и ЧМ/ЦШ А.Денисова, только из нее нужно исключить все то, что связано с клавиатурой (ее просто нет).
Эту же программу вполне можно использовать как учебно-тренировочную, но, самое главное, разберитесь с ее конструкцией.
Для тех, кто изучил предыдущие разделы, эта работа не будет связана с большим трудом, так как в программе Kea.asm Вы увидите то, о чем подробно рассказывалось ранее.
На данном этапе, главное - ощутить, как из "блоков" различного функционального предназначения создается "нечто", выполняющее какие-то "разумные" действия, и как эти "блоки" между собой взаимодействуют.
Эта своеобразная "музыка" программирования, по-началу, может показаться чем-то сложным, но, если речь идет о полноценном творчестве, то с ней обязательно нужно разобраться.
Труд, конечно, не из легких, но, в итоге, Вы получите "власть" над программой со всеми вытекающими последствиями.
Поверьте мне, это очень приятное чувство, и не столько из-за ощущения этой самой "власти", а, главным образом, из-за ощущения того, что ты вовсе не являешься бестолковым человеком, и впереди у тебя масса интересной и перспективной работы, а это, согласитесь со мной, делает жизнь гораздо более интересной.
Программу Kea.asm, частично или полностью, можно сделать базовой для более сложных устройств, вариантов здесь может быть множество, но перед тем, как это делать, советую Вам капитальнейшим образом с ней разобраться.
Для того чтобы облегчить Вам эти "разборки", я составил блок-схему программы Kea.asm (файл с названием Kea.doc)
На всякий случай, для тех людей, которые не имеют MPLAB, я "загнал" текст программы Kea.asm в "Вордовский" текстовый редактор из комплекта "Офиса" (название файла KeaASM.doc).
Ниже Вы можете скачать эти файлы:

Файл Kea.asm: здесь.
Файл Kea.doc: здесь.
Файл KeaASM.doc: здесь.

Если Вы не только дочитали до этого места, но и усвоили изложенный выше материал, то можете переводить себя в следующую "весовую категорию": из "начинающих программистов" в "начавших программистов".
"Курс молодого бойца" закончен, и теперь Вы являетесь "жизнеспособным организмом", вполне способным к самостоятельному развитию.
Если вопросов стало еще больше, чем изначально, то это абсолютно нормально и естественно, так как тема неисчерпаема и знать все просто физически невозможно.
Среди множества возможных направлений выберете себе одно и "долбите в одну точку" до тех пор, пока не наступит "просветление", затем выберете другое ... и т.д. ("Москва не сразу строилась).
Так или иначе, но "успех всего предприятия" сильно зависит от Вашей первой самостоятельно составленной программы.
Еще раз обращаю Ваше внимание, что программа составляется под конкретное устройство, и для того чтобы ее составить, нужно совместить в одном лице программиста и электронщика.
Только в этом случае может идти речь о конструировании, то есть, о полноценном процессе творчества.
"Однобокость" типа "чистого" программиста или "чистого" электронщика, лично у меня, вызывает ассоциацию с какой-то незавершенностью, "недоделанностью".
Как это не прискорбно, но большое количество умнейших людей страдают этой "однобокостью", осознают ее и комплексуют по этому поводу.
С целью помочь этим людям и задуман "Самоучитель...".
Если речь зашла о конструировании, то без того, что называют "техническим заданием на разработку устройства" или "заданием на разработку", не обойтись, ведь именно с него все и начинается.
Ниже будут сформулированы такие технические задания с постепенным их усложнением, и я рекомендую Вам составить свою первую самостоятельную программу именно по этим заданиям.
Техническое задание на разработку устройства № 1.
После включения питания, устройство должно формировать прерывистый тональный сигнал (нагрузка - пьезоэлектрический излучатель), то есть, "пачки" импульсов с паузами между ними.
Частота тонального сигнала 3 кГц.
Время формирования одной "пачки" импульсов и время формирования паузы между двумя соседними "пачками" импульсов одинаково - по 0,3 сек.
Усложняем устройство № 1:
Техническое задание на разработку устройства № 2.
После включения питания, устройство должно формировать двухтональный сигнал с частотами 3 кГц. и 1,5 кГц.
Продолжительность "пачек" импульсов (для обеих частот) - по 0,3 сек.
Усложняем устройство № 2:
Техническое задание на разработку устройства № 3.
То же самое, что и для устройства № 2, но с "привлечением" клавиатуры из одной кнопки с нормально разомкнутыми контактами.
После включения питания, устройство должно "молчать".
При кратковременном нажатии на кнопку, на протяжении примерно 3 ... 5 секунд после нажатия, должен формироваться двухтональный сигнал с указанными выше параметрами, после чего устройство должно перейти в режим ожидания седующего нажатия кнопки.
После того, как Вы составите хотя бы одну из этих программ, наступит значительное "просветление" в части касающейся кольцевой "природы" программ.
Рекомендую это сделать.
Если кто-то из Вас последует этому совету, то, если есть такое желание, можете прислать мне ASM-файл Вашей программы для "разбора полетов" или написать о трудностях возникших в процессе ее составления. Помогу чем смогу.
Конечно же, эти программы и далее можно усложнять, последовательно вводя в них те или иные "навороты", что является иллюстрацией того, как, имея простенький "скелет" программы, путем последовательного наращивания на него "мяса", можно создать программу, которую уже простенькой не назовешь.
Обычно так и создаются сложные программы.
Несколько слов об ассемблере.
Вы уже должны понять, что ассемблер это язык электронщиков, и без знания, как минимум, основ цифровой техники, он подобен компьютеру без операционной системы.
В руках толкового электронщика он превращается в мощнейший "инструмент", при помощи которого можно делать большие дела.
Как говорится, "все гениальное просто".
Пусть ассемблер и не очень комфортен, но, по "чистоте" программы, он сильно опережает любой из языков высокого уровня, причем, чем выше уровень языка, тем это опережение еще более значительно.
Проще говоря, языки высокого уровня создают в тексте программы много "мусора", который увеличивает (и значительно) объем программы и ухудшает технические характеристики устройства, под которое эта программа создана.
Простой пример: если, например, реализовать программу ЧМ/ЦШ на языке С (кстати, в MPLAB имеется возможность работы с языком С), то мерцания будут ощущаться сильнее, чем если сделать то же самое в ассемблере, и, к тому же, возникает проблема типа "а хватит ли места в памяти программ?"
Таким образом, по качеству конечного результата, ассемблер (как "инструмент" в руках умелого программиста) вне конкуренции, и если речь заходит о необходимости создания качественной программы небольшого объема, то и выбор однозначен.
Языки высокого уровня хороши для мощных быстродействующих процессоров типа тех, которые используются в компьютерах, они "проглотят" любой "мусор" и не "поперхнутся", а что касается маломощных процессоров типа микроконтроллеров, то программы для них, во всех отношениях, выгоднее составлять в ассемблере.
Языки высокого уровня "плодятся как кролики", а ассемблер, он и в Африке ассемблер, и в нем можно спокойно и профессионально совершенствоваться, не опасаясь "революционной" смены "правил игры".
В следующем разделе Вы найдете то, что может оказаться полезным в дальнейшей работе.
Переходите к следующему разделу.


Kea_39

19. Разные "полезности".

Примечание: расположенные ниже ссылки не работают, но они работают в 19-м разделе "Самоучителя..." на моем сайте, откуда и нужно скачивать соответствующие файлы.

1. MPLAB IDE. Руководство пользователя на русском языке. Файл MPLAB.pdf объемом
2,77 Мб.
Файл MPLAB.pdf Вы можете скачать: здесь.
2. MPASM. Руководство пользователя на русском языке. Файл MPASM.pdf объемом
920 Кб.
Файл MPASM.pdf Вы можете скачать: здесь.
3. Справочник по среднему семейству (слово = 14 бит) микроконтроллеров PICmicro на
русском языке.
 Исключительно полезная информация, причем, универсального характера, расчитанная на весь "набор" ПИКов, от самых простых до самых "навороченных".
Разделы, посвященные модулям, которые Вы не будете использовать, можно просто не читать, а ознакомиться с ними позднее, по мере возникновения в этом необходимости.
Всего в справочнике 34 раздела, которые можно закачать каждый по-отдельности, но я советую Вам сразу закачать их все (общий объем закачки порядка 12,7 Мб.).
Вы попадете в то место, где можно закачать разделы этого "Справочника...", щелкнув:
здесь.
4. Техническая документация на PIC16F84A на английском языке.
Техническую документацию на русском языке, к сожалению, мне найти не удалось.
Если кто-то знает место, где она "лежит", то дайте мне "наводку", и я заменю этот файл.
Файл технической документации 35007b.pdf , объемом 1,45 Мб., Вы можете скачать: здесь.
5. А.Торрес. Аппаратные хитрости использования PIC-контроллеров.
Файл этой статьи Usepic_p.doc, объемом 226 Кб. (на русском языке), Вы можете скачать:
здесь.
6. Дизассемблер для ПИКов. Это такая хакерская "штучка", которая восстанавливает текст программы (ASM-файл) по HEX-файлу. Исключительно полезна для людей, желающих "покопаться" в тексте программы, если в наличии имеется только HEX-файл.
"Наводку" на него мне дал один из посетителей сайта, и этот дизассемблер мне так понравился, что мне захотелось, чтобы и другие его по достоинству оценили.
Основное его удобство - в крайней простоте работы с ним: открываете интересующий Вас HEX-файл, щелкаете по кнопке "А" (ASM-файл) и все, текст программы перед Вами.
Естественно, он будет немного "неудобоварим" (комментариев нет, названия подпрограмм и меток обезличены и нумеруются и т.д.), но все команды на месте и, если над этим делом "попыхтеть", то вполне можно разложить программу на "молекулы" и понять, как она работает.
Нажали на кнопку "H" (HEX-файл), и перед Вами то, что предназначено для "зашивки" в ПИК.
Этот дизассемблер не является отдельной программой, а встроен в программу, обслуживающую работу программатора ICprog105.
Лично я, пользуюсь PonyProg, а из ICprog105 мне нужен только дизассемблер, но если кому-то он (ICprog105) понравится, то по адресу www.ic-prog.com Вы найдете информацию, необходимую для его сборки.
Архив программы icprog105C.zip объемом 551 Кб., Вы можете скачать: здесь.
С программой можно работать сразу же после разархивации. Единственное, что нужно сделать - создать ее ярлык и поместить его в удобное для Вас место.


Kea_50

Дополнительно

Поступает много вопросов по задержкам программы Multi.asm, связанных с величиной задержки.
При применении кварца на 4 мГц, максимально возможная частота мультивибратора составляет 50 кГц.
Если нужно получить частоту выше чем 50 кГц, то нужно применить кварц с частотой более 4 мГц, но менее 10 мГц.
Если нужно получить частоту порядка нескольких герц или долей герца, нужно применить не одноразрядный, а 2-х или 3-хразрядный счетчик.
Для решения большинства задач, связанных с достаточно низкими частотами (с относительно большими задержками), обычно, достаточно и 2-хразрядного счетчика (его работа достаточно подробно описана при "разборках" с программой cus.asm).
Обращаю Ваше внимание, что 2 последовательно соединенных одноразрядных счетчика не есть 2-хразрядный счетчик.
"Настоящим" 2-хразрядным счетчиком можно обеспечить гораздо большие (ударение на "о") задержки, чем в случае применения 2-х последовательно соединенных одноразрядных счетчиков.
Информация по работе с портами
 Не стоит пытаться управлять защелками выводов портов из "шапки" программы, из этого ничего путного не получится.
После "прописки" регистров TrisA, PortA, TrisB, PortB (а также и других регистров специального назначения) по адресам, указанным в распечатке области оперативной памяти, про эти строки можно вообще "забыть".
Управление защелками портов осуществляется только в рабочей части программы.
Если нужно изменить состояние только одной из защелок - используются бит - ориентированные команды bcf или bsf.
Если нужно изменить состояния сразу нескольких защелок (или вообще всех) - используются байт - ориентированные команды.
Например, необходимо установить на выводах RB0, RB1, RB2, RB3, RB4, RB5, RB6, RB7: 1, 0, 0, 1, 1, 0, 0, 0 соответственно.
Следовательно, нужно записать в регистр PortB число b'00011001' (.25 или 19h).
Непосредственно, записать константу в регистр PortB нельзя, значит производим эту операцию через регистр W.
Делается это так:
movlw b'00011001'
movwf PortB
Вместо числа b'00011001' можно использовать число .25 или 19h.
Обращаю внимание, что выходы защелок подключаются к соответствующим выводам портов только тогда, когда эти выводы настроены на работу "на выход".
От выводов портов, настроенных на работу "на вход", выходы соответствующих защелок отключены.
Переключением этих настроек "рулят" биты регистров TrisA и TrisB.
Вывод из этого следующий: программа "способна" управлять состояниями (0 или 1) только тех выводов портов, которые настроены ей же (то есть программой) на работу "на выход", а состояния выводов портов, настроенных программой на работу "на вход" будут определяться внешними источниками сигналов.
Если вернуться к приведенному выше примеру и предположить, что, например, выводы RB0, RB1, RB2, RB3 настроены на работу "на выход", а выводы RB4, RB5, RB6, RB7 настроены на работу "на вход", то, после выполнения указанных выше двух команд, на выводах RB0, RB1, RB2, RB3 установятся 1, 0, 0, 1 соответственно, а состояния выводов RB4, RB5, RB6, RB7 будут определяться внешними источниками сигналов, не смотря на то, что на выходах защелок этих выводов установятся 1, 0, 0, 0 соответственно (по большому счету, толку от этой установки - никакого, так же, как и от установки любой комбинации нулей и единиц, ведь выходы этих защелок к выводам порта не подключены).
Программа Multi.
Вопрос: какой смысл каждый полный цикл программы исполнять первые 4 команды ПП Start, ведь исполнив их один раз (при включении питания), далее их можно не исполнять, поставив метку на команде movlw .32 и осуществляя именно на нее безусловный переход, заменив команду goto Start (в рабочей части программы) на команду goto "название метки"?
Ответ: смысл самый что ни на есть практический.
Представьте себе достаточно обычную ситуацию: плохой контакт с батарейкой, или по сети 220v "прошла" импульсная помеха в то время, когда мультивибратор работает.
Если при этом произойдет изменение содержимого регистров Status или TrisB, то устройство может перестать работать (простейший случай зависания программы).
В этом случае, восстановить работоспособность устройства можно только после выключения и последующего включения питания.
Если же безусловный переход осуществляется на ПП Start, то, в случае воздействия импульсной помехи, сбой, конечно, также может произойти, но работоспособность устройства очень быстро восстановится без каких бы то ни было манипуляций с выключателем питания.
Дополнительная информация по директиве EQU
При присвоении битам регистров специального назначения их названий (в "шапке" программы), названия битов повторяться (дублироваться) не должны, но номера битов повторяться могут.
Например, биту № 0 можно одновременно присвоить названия С и RBIF (С equ 0 и RBIF equ 0).
В этом случае, если рабочая часть команды - STATUS,C, то команда обратится к биту № 0 регистра STATUS, а если рабочая часть команды - INTCON,RBIF, то команда обратится к биту № 0 регистра INTCON.
В конце 14-го раздела Вы можете скачать файл программы - перекодировщика BinHexDec.exe.
Она хороша тем, что позволяет визуально отследить сдвиги вправо и влево, и в ней можно наблюдать изменения значений чисел, которые при этом происходят.
В "шапках" текстов программ, а, следовательно, и в рабочих частях программ, название регистра специального назначения OPTION обязательно должно иметь в своем названии букву R.
Лучше всего это название писать так: OptionR.
Если название этого регистра не будет содержать буквы R, то, после ассемблирования текста программы, MPLAB выдаст сообщение об ошибке и HEX-файл создан не будет.