Применение подпрограмм Печать

Вернуться к оглавлению

Применение подпрограмм при программировании

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

Участок программы, к которому можно обращаться из различных мест программы для выполнения некоторых действий называется подпрограммой

Проблема, с которой приходится сталкиваться при многократном использовании участков кодов, это в какое место памяти программ возвращаться после завершения подпрограммы? Обращение к подпрограмме производится из нескольких мест основной программы. Описанную ситуацию иллюстрирует рисунок 1. На этом рисунке изображено адресное пространство микроконтроллера. Младшие адреса адресного пространства на этом рисунке находятся в нижней части рисунка.

Рисунок 1. Вызов подпрограммы и возврат к выполнению основной программы.

Для того, чтобы получить возможность возвращаться на команду, следующую за командой вызова подпрограммы, требуется запомнить адрес этой команды. Адрес возврата хранится в особых ячейках памяти. После выполнения  подпрограммы необходимо осуществить переход к адресу, который записан в этих ячейках.

Для обращения к подпрограмме и возврата из неё в систему команд микропроцессоров вводят специальные команды. В микроконтроллерах семейства MCS-51 это команды LCALL, ACALL для вызова подпрограммы и команда RET для возврата из подпрограммы. Эти команды не только осуществляют передачу управления на указанный адрес, но и запоминают адрес команды, следующей за командой вызова подпрограммы. Команда возврата из подпрограммы RET передаёт управление команде, адрес которой  был запомнен командой вызова подпрограммы. Пример использования подпрограммы на языке программирования ASM-51 приведён на рисунке 2:

  ...
  MOV A,#56
  CALL PeredatByte
  ...
  MOV A,#37
  CALL PeredatByte
  ...
 
;********************************************
;Подпрограмма передачи байта
;через последовательный порт
;********************************************
PeredatByte:
  JB TI,$          ;Если предыдущий байт передан
 
 
  MOV SBUF,G_Per   ;то передать очередной байт
  RET

Рисунок 2. Пример вызова подпрограммы.

Внимание! Ни в коем случае нельзя попадать в подпрограмму любым способом кроме команды вызова подпрограммы CALL! В противном случае команда возврата из подпрограммы передаст управление случайному адресу! По этому адресу могут быть расположены данные, которые в этом случае будут интерпретированы как программа, или обратиться к внешней памяти, откуда будут считываться случайные числа.

Очень часто требуется из одной подпрограммы обращаться к другой подпрограмме. Такое обращение к подпрограмме называется вложенным. Количество вложенных подпрограмм называется уровнем вложенности подпрограмм. Максимально допустимый уровень вложенности подпрограмм определяется количеством ячеек памяти, предназначенных для хранения адресов возврата из подпрограмм.

Ячейки памяти, в которых хранятся адреса возврата из подпрограмм называются стеком. Логически эти ячейки памяти организованы так, чтобы считывание последнего записанного адреса производилось первым, а первого записанного адреса производилось последним. Такая логическая организация формируется специальным счётчиком. Этот счётчик называется указателем стека SP. Ячейка памяти, в которую в данный момент может быть записан адрес возврата из подпрограммы, называется вершиной стека. Количество ячеек памяти, предназначенных для организации стека, называется глубиной стека. Последняя ячейка памяти, в которую можно производить запись называется дном стека. Логическая организация стека приведена на следующем рисунке:

Рисунок 3. Организация стека в памяти данных микропроцессора.

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

Например в микроконтроллерах семейства MCS-51 при занесении информации в стек содержимое указателя стека увеличивается (стек растёт вверх), поэтому стек размещается в самой верхней части памяти данных. Для того, чтобы установить глубину стека 28 байт, необходимо вычесть из адреса максимальной ячейки внутренней памяти микроконтроллера глубину стека и записать полученное значение в указатель стека SP:

Кроме содержимого программного счётчика часто требуется запоминать содержимое внутренних регистров и флагов процессора, локальных переменных подпрограммы. Стек оказался удобным средством и для этой задачи. Сохранение локальных переменных в стеке позволило осуществлять вызов подпрограммы самой из себя (реализовывать рекурсивные алгоритмы). Это привело к введению в систему команд специальных команд работы со стеком. В микроконтроллерах семейства MCS-51 это команды PUSH и POP. Использование этих команд показывается на следующем примере:

Вернуться к оглавлению