Вход в программный модуль можно выполнить посредством вызова этого модуля из другой части программы или по некоторому аппаратному событию, внешнему по отношению к ЦПУ. Этим событием может быть напряжение заданного уровня на одном из выводов процессора или же сигнал от внутреннего периферийного устройства, например признак переполнения в модуле таймера. В первом случае программный модуль называется
Пока же мы займемся подпрограммами.
Аналогом подпрограмм в аппаратуре являются платы расширения. Предположим, что нам необходимо реализовать задержку длительностью 1 мс. Эта задержка может потребоваться при генерации тонального сигнала частотой 500 Гц, чтобы пилот самолета обратил внимание на предупреждающие сигналы панели управления, например о низком уровне топлива или перегреве двигателей. В модульной программе эта задержка может быть реализована отдельной подпрограммой, которая будет вызываться из основной программы по мере необходимости, скажем, для периодического переключения состояния вывода порта с ВЫСОКОГО на НИЗКОЕ на время длительностью 1 мс. Эта ситуация показана на Рис. 6.2.
Рис. 6.2.
Вообще говоря, операция вызова подпрограммы заключается в простой записи адреса первой команды подпрограммы в счетчик команд (РС), как это делалось и в команде goto. Так, если наша подпрограмма расположена, начиная с адреса h’400’, то может показаться, что команда goto h’400’ выполнит требуемое действие. Если предположить, что точка входа в подпрограмму обозначена меткой DELAY_1MS, как в Программе 6.1, мы получим команду goto DELAY_1MS.
Теперь перед нами встает проблема — как вернуться обратно? Каким-то образом микроконтроллер должен запомнить то место в программе, откуда он перешел к подпрограмме, чтобы вернуться к следующей команде вызывающей программы. Эта ситуация показана на Рис. 6.2, причем вызов подпрограммы может осуществляться из любого места основной программы или даже из другой подпрограммы (см. Рис. 6.4).
Один из вариантов решения указанной проблемы заключается в запоминании этого адреса возврата в специальном регистре или ячейке памяти данных перед переходом к подпрограмме. А для возврата это значение может быть загружено обратно в счетчик команд при завершении подпрограммы. Указанный способ перестает работать в случае вызова одной подпрограммы из другой. В результате вторая подпрограмма перезапишет адрес, сохраненный первой подпрограммой, и возврата в основную программу никогда не произойдет. Этого можно избежать, если задействовать под стек адресов возврата более одного регистра или ячейки памяти. Структура такого стека типа LIFO (последним вошел — первым вышел) показана на Рис. 6.3,
Рис. 6.3.
В микроконтроллерах PIC с 14-битным ядром стек реализован в виде восьми 13-битных регистров, которые используются исключительно для хранения адресов возврата из подпрограмм[91]. Структура, показанная на Рис. 6.3, называется также
С данным стеком связан 3-битный счетчик, который указывает на следующий свободный регистр в стеке. Этот регистр
Это значение является адресом команды, следующей за командой call, поскольку РС уже был инкрементирован, и эта следующая команда была загружена в конвейер одновременно с выполнением команды call (см. Рис. 4.4 на стр. 92).
На Рис. 6.3,
1. Содержимое счетчика команд загружается в ячейку стека, на которую указывает указатель стека SP. Это сохраненное значение является адресом команды, следующей за командой call.
2. Инкрементируется указатель стека.
3. Адрес назначения DELAY_1MS, представляющий собой адрес точки входа в подпрограмму, перезаписывает исходное содержимое РС. Это приводит к передаче управления в подпрограмму