Хорошо написанное программное обеспечение должно представлять собой совокупность взаимодействующих модулей, а не одну большую программу, выполняющую все задачи от начала до конца. У модульного принципа имеется множество достоинств, без использования которых практически нельзя обойтись, когда размер кода становится больше сотни строк или когда программа разрабатывается несколькими программистами.
Таким образом, для написания программы необходимо разбить общую задачу на несколько процедур, каждая из которых будет четко выполнять поставленную задачу. Такого рода модуль должен быть небольшого размера, хорошо документирован и легок для понимания, причем не только для написавшего его программиста.
Преимущества модульного программирования:
· Модули можно тестировать и отлаживать отдельно от других
· можно повторно использовать модули из других программ
· легче модернизировать программу (простой заменой модулей)
Решение о том, каким образом разбить программу на модули принимается на основе опыта. Для реализации подпрограмм имеется ряд дополнительных команд.
Вход в программный модуль может выполнить посредством вызова этого модуля из другой части программы или по некоторому аппаратному событию, внешнему по отношению к ЦПУ. Этим событием может быть напряжение заданного уровня на одном из выводов микроконтроллера или же сигнал от внутреннего периферийного устройства, например, признак переполнеия в модуле таймера.
В первом случае программный модуль назывеется подпрограммой, во втором случае речь идет о подпрограмме прерывания. Принципы написания этих модулей, а также процедуры входа и выхода из них настолько различны,что прерывания рассмотрены в следующей главе.
Рис.9.1. Вызов подпрограммы
Операция вызова подпрограммы заключается в простой записи адреса превой команды подпрoграммы в программный счетчик РС. Если предположить, что точка входа в подпрограмму обозначена меткой DELAY_1 мс , мы получим команду CALL DELAY_1 мс.
.
Рис. 9.2. Стек микроконтроллера PIC16F84A
А как вернуться обратно? Каким образом микроконтроллер запомнит место в программе, откуда он перешел к подпрограмме, чтобы вернуться к следующей команде вызывающей программы? Эта сутуация показана на Рис.9.1.
Один из вариантов решения проблемы заключается в запоминании адреса возврата в специальной области памяти – стеке перед переходом к подпрограмме. А для возврата это значение может быть загружено обратно в счетчик команд при завершении подпрограммы.
Структура такого стека, работающего по принципу LIFO ("последний пришел — первый ушел") показана на Рис.9.2.С данным стеком связан 3-разрядный счетчик SP (Stack Point) - указатель стека. Он не может быть изменен с помощью какой-либо команды, а автоматически инкрементируется при каждом исполнении команды CALL. Кроме того, перед записью заданного адреса в счетчик команд заносит его текущее значение в стек. Это значение является адресом команды, следующей за командой CALL.
На Рис. 9.2. б показано состояние, возникающее после вызова подпрограммы DELAY_1ms:
1. Содержимое счетчика команд загружается в ячейку стека, на которую показывает указатель стека SP. Это значение является адресом команды, следующей за командой CALL.
2. Инкрементируется указатель стека.
3. Адрес назначения DELAY_1ms, представляющий собой адрес точки вода в подпрограмму, заносится в РС.
Командой, завершающей подпрограмму должна быть команда RETURN. Эта команда извлекает адрес возврата из стека и помещает его в счетчик команд, как показано на Рис.9.2.в. исполнение команды RETURN происходит следующим образом:
1. Декрементируется указатель стека.
2. 13-битный адрес, адресуемый указателем стека, копируется из стека в счетчик команд.
Таким образом, независимо от того, откуда была вызвана подпрограмма, сразу после ее завершения выполнение вернется к команде, следующей за командой CALL.
Команда RETLW K похожа на команду RETURN, за исключением того, что при выполнении помещает заданное в команде число K в W-регистр.
Стек поддерживает вложенные подпрограммы. На Рис.9.3. основная программа вызывает подпрограмму первого уровня SR1, которая, в свою очередь, вызывает подпрограмму второго уровня SR2. Чтобы, в конечном счете вернуться обратно в основную программу, последовательность действий при возврате должна в точности соответствовать последовательности действий при входе. Это обеспечивается структурой стека, работающего по принципу: первым вошел - последным вышел.
Рис. 9.3. Вложенные подпрограммы
Подпрограммы можно хранить в библиотеках и использовать в разных программах. Короче говоря, подпрограммы обеспечивают все удобства модульного программирования и делают зто достаточно гибко.
Основной недостаток подпрограмм заключается в необходимости дополнительного кода для объединения их так, чтобы они могли взаимодействовать друг с другом. Этот дополнительный код называется связыванием подпрограмм.