Почти всё то что мы рассматривали в учебном курсе ранее, справедливо не только для микропаскаля, но и для «большого» паскаля для ПК. Начиная с этой части так же будут рассматриваться некоторые специфические особенности паскаля для AVR. Но начнем мы с пары слов о пользовательских функциях и процедурах в микропаскале.  Функция или процедура это некоторый код который может быть вызван из любой части основной программы с какими либо параметрами (или без них). После того как процедура или функция отработала, управление возвращается основной программе. Между функциями и процедурами всего одно различие: Функция может возвращать какое либо значение, а процедура не может.  Функция выглядит следующим образом:

Function ИмяФункции(СписокПараметров):ТипВозвращаемогоЗначения;
begin
  result:=ВозвращаемоеЗначение;
end;

Имя функции любое не содержащее пробелов и спецсимволов. Именно по этому имени происходит обращение к функции, поэтому для улучшения читаемости кода рекомендуется давать осмысленные имена функциям и процедурам. Список параметров — список переменных которые передаются в функцию из вне. Тип возвращаемого значения — тип данных возвращаемого функцией значения. Функция должна обязательно возвратить значение, для этого нужно записать в переменную result возвращаемое значение. Если в теле функции нигде нет присвоения значения этой переменной, то проект даже не скомпилируется. Вместо result можно так же использовать имя самой функции. Отдельное внимание стоит уделить списку параметров. Существует два способа передать в функцию что-либо: С использованием параметров-значений или с использованием параметров-переменных. Рассмотрим оба варианта начиная с использования параметра-переменной:

program Funct;
var p:byte;
 
Function MyFunction(var a:byte):byte;
begin
  a:=a+10;
  result:=a;
end;
 
begin
   DDRA:=0xFF;
   p:=123;
   PORTA:=MyFunction(p);
end.

В данном случае в функцию передается переменная и функция может изменять её! Это значит что когда программа отработает, в переменной p уже будет не 123, а 133. Если же мы уберем слово var из описания списка передаваемых параметров, то в функцию передастся всего лишь её значение переменной. И что бы с ней не вытворяли внутри функции переменная p как была так и останется равной 123. Различие между параметрами-переменными и параметрами-значениям только в этом. Для процедур в микропаскале справедливо всё сказанное выше за исключением того что процедуры ничего не возвращают. Немного перепишем наш пример для использования в нем не функции а процедуры:

program Proc;
var p:byte;
 
Procedure MyProcedure(var a:byte);
begin
  a:=a+10;
end;
 
begin
   DDRA:=0xFF;
   p:=123;
   MyProcedure(p);
   PORTA:=p;
end.

Здесь у нас в процедуру передается переменная p которую изменяет тело процедуры прибавляя к её значению 10. В результате в PORTA будет выведено число 133.

Теперь можно перейти к прерываниям. Если кто не помнит что такое прерывание то напоминаю: Прерывание это некоторое событие вызываемое периферией контроллера (таймеры, ацп, EEPROM итд). Когда такое событие наступает, (таймер отсчитал определённый промежуток времени, ацп завершил измерение напряжения, EEPROM готова к работе итд) то основная программа на время перестаёт выполняться и управление передаётся к команде хранящейся в памяти по строго определённому адресу (Называется вектор прерывания и для каждого прерывания свой). Естественно возникает вопрос как объявить обработчик прерывания в микропаскале? Делается это следующим образом. Предположим, что хотим обрабатывать прерывание по переполнению от таймера 0 на микроконтроллере Atmega32. Открываем даташит и смотрем по какому адресу расположен вектор этого прерывания. В данном случае это адрес 0x16. Теперь пишем процедуру которая и будет обработчиком прерывания:

procedure Timer0_Interrupt(); iv 0x16;
  begin
    ///Тело обработчика прерывания
  end;

Название процедуры обработчика может быть произвольное. Лезть каждый раз в даташит чтобы узнать адрес вектора прерывания не совсем удобно. Поэтому у каждого вектора прерывания есть своё название на нормальном человеческом языке. В целях переносимости кода лучше всего использовать именно их т.к. у всех контроллеров они называются одинаково, а адреса векторов вполне могут быть разными. Для того чтоб посмотреть список названий векторов прерываний можно например открыть файл  ATmega32.mpas в папке \mikroPascal PRO for AVR\Defs\ . Там мы найдем следующее:

const IVT_ADDR_RESET            = 0x0000;
const IVT_ADDR_INT0             = 0x0002;
const IVT_ADDR_INT1             = 0x0004;
const IVT_ADDR_INT2             = 0x0006;
const IVT_ADDR_TIMER2_COMP      = 0x0008;
const IVT_ADDR_TIMER2_OVF       = 0x000A;
const IVT_ADDR_TIMER1_CAPT      = 0x000C;
const IVT_ADDR_TIMER1_COMPA     = 0x000E;
const IVT_ADDR_TIMER1_COMPB     = 0x0010;
const IVT_ADDR_TIMER1_OVF       = 0x0012;
const IVT_ADDR_TIMER0_COMP      = 0x0014;
const IVT_ADDR_TIMER0_OVF       = 0x0016;
const IVT_ADDR_SPI__STC         = 0x0018;
const IVT_ADDR_USART__RXC       = 0x001A;
const IVT_ADDR_USART__UDRE      = 0x001C;
const IVT_ADDR_USART__TXC       = 0x001E;
const IVT_ADDR_ADC              = 0x0020;
const IVT_ADDR_EE_RDY           = 0x0022;
const IVT_ADDR_ANA_COMP         = 0x0024;
const IVT_ADDR_TWI              = 0x0026;
const IVT_ADDR_SPM_RDY          = 0x0028;

Это и есть ни что иное как таблица векторов прерываний для Atmega32. Ну разумеется что лезть в файл каждый раз тоже ни чуть не проще чем в даташит, поэтому когда пишем procedure Timer0_Interrupt(); iv ставим пробел после iv и жмем комбинацию CTRL + Пробел и пишем IVT после этого редактор кода сам предложит дописать имя вектора прерывания. Разумеется для работы с прерываниями надо их сначала разрешить. Чтоб их разрешить нужно установить бит I в регистре SREG. Работать с отдельными битами регистра в микропаскале очень просто. Например чтоб установить бит I (седьмой по счёту) нужно написать SREG.B7:=1 или что еще понятней SREG_I_bit := 1;. Для того чтоб получше себе представить как работать с прерываниями вот небольшой пример (мигание светодиодом):

program Blinker;
 
procedure Timer0_Interrupt(); iv IVT_ADDR_TIMER0_OVF;
  begin
    PORTA:=NOT PORTA; ///Инвертируем состояние порта
  end;
 
begin
 SREG_I_bit := 1; //Разрешаем прерывания
 TCCR0.CS00:=1;   //Включаем таймер0
 TCCR0.CS02:=1;   //с делителем на 1024
 TIMSK.TOIE0:=1;  //Разрешаем прерывание по переполнению
 DDRA:=0xFF;      //Порт А выход
end.

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

program Blinker;
begin
 DDRA:=0xFF;  //Порт на выход
 
 while(TRUE) do   ///Бесконечный цикл
  Begin
      asm
        ldi R16,0xFF
        out PORTA,R16    //Выводим 0xFF в порт
      end;
      delay_ms(500);     // Ждем
      asm
        ldi R16,0x00
        out PORTA,R16     //Выводим 0x00 в порт
      end;
      delay_ms(500);      // Ждем
  end;
end.

Однако ассемблерных вставок стоит избегать, и использовать их в случае крайней необходимости, т.к. это очень сильно влияет на переносимость кода с одного микроконтроллера на другой. Кстати если требуется вставить в код пустую команду NOP то сделать это можно без использования всяких вставок (просто пишем NOP; и всё).  Иногда может возникнуть необходимость использовать в ассемблерной вставке какую либо переменную. Компилятор располагает все переменные по умолчанию в оперативной памяти, и адрес любой из них можно выяснить в окне Statistics на вкладке Variables. Если для удобства хочется чтобы компилятор разметил переменную в регистре, то нужно явно сказать ему об этом при объявлении переменной:

var i:byte; rx;

Но и тут есть некоторые недостатки. Размещать переменные в регистрах он начинает с регистра R2. А как известно регистры R0-R15 в AVR являются немного ущербными (с ними не работают некоторые команды например ldi) . Заставить компилятор хранить переменную в конкретном регистре мне не удалось. Кстати нужно быть аккуратным при использовании регистров в ассемблерных вставках, мало ли чего в них положил компилятор для своих нужд. Лучше вообще предварительно запихивать их значения в стек, а потом когда регистр нам не нужен восстанавливать значение из стека.

P.S. В следующей части пойдёт рассказ о работе с периферией контроллера (АЦП, ЕЕПРОМ, UART итд)
P.P.S. Я сейчас активно пишу научный труд под названием диплом, именно поэтому с обновлениями возникают задержки. Но это временное явление :-)