Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

691_Mikushin_A.V._Programmirovanie_mikroprotsessorov_

.pdf
Скачиваний:
48
Добавлен:
12.11.2022
Размер:
1.96 Mб
Скачать

/* Правильное использование параметров функции */ void change (int *x, int *y)

{int k=*x; *x=*y; *y=k;

}

При вызове такой функции в качестве фактических параметров должны быть использованы не значения переменных, а их адреса:

change (&a,&b);

Предварительное объявление подпрограмм.

В языке С-51 нет требования, чтобы определение функции обязательно предшествовало ее вызову. Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле. Однако для того, чтобы компилятор мог осуществить проверку соответствия типов передаваемых фактических параметров типам формальных параметров и, если необходимо, выполнил соответствующие преобразования, до вызова функции нужно поместить объявление (прототип) функции.

Если объявление функции не задано, то по умолчанию строится прототип функции на основе анализа первого вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров, используемых при данном вызове. Однако такой прототип не всегда согласуется с последующим определением функции. При размещении функции в другом файле или после оператора ее вызова рекомендуется задавать прототип функции. Это позволит компилятору либо выдавать диагностические сообщения, при неправильном использовании функции, либо правильным образом преобразовывать типы аргументов при её вызове.

Прототип – это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции. Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Объявление (прототип) функции имеет следующий формат:

[спецификатор класса памяти] [спецификатор типа] имя функции ([список формальных параметров]);

В отличие от определения функции, в прототипе за заголовком сразу же следует точка с запятой, а тело функции отсутствует. Правила использования остальных элементов формата такие же, как и при определении функции.

Для функции, определенной в предыдущем параграфе, прототип может быть записан в следующем виде:

int rus (unsigned char r);

61

При этом в объявлении функции имена формальных параметров могут быть опущены:

int rus (unsigned char);

Вызов функций.

Вызов функции имеет следующий формат:

имя функции ([список выражений]);

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

DecodSostKn();

VypFunc();

Функция, если она возвращает какое-либо значение (подпрограммафункция), может быть вызвана и в составе выражения, например:

y=sin(x);

//sin

-

это

имя

подпрограммы-функции

if(rus(c))SvDiod=Gorit;

//rus

-

это

имя

подпрограммы-функции

Выполнение вызова функции происходит следующим образом:

1.Вычисляются выражения в списке выражений и подвергаются обычным арифметическим преобразованиям. Затем тип полученного фактического аргумента сравнивается с типом соответствующего формального параметра. Если они не совпадают, то либо производится преобразование типов, либо формируется сообщение об ошибке. Число выражений в списке выражений должно совпадать с числом формальных параметров. Если в прототипе функции указано, что ей не требуются параметры, а при вызове они указаны, формируется сообщение об ошибке.

2.Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3.Управление передается на первый оператор функции.

Поскольку имя функции является адресом начала тела функции, то в качестве обращения к функции может быть использовано не только имя функции, но и значение указателя на функцию. Это значит, что функция может быть вызвана через указатель на функцию. Пример объявления указателя на функцию:

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значе-

62

/* инициализация указателя на функцию */ /* обращение к функции */
fun возвращающей указа-

ние типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись

int *fun (int x,int *y);

будет интерпретироваться как объявление функции тель на int.

Вызов функции возможен только после инициализации значения указателя:

float (*funPtr)(int x, int y); float fun2(int k, int l);

...

funPtr=fun2;

(*funPtr)(2,7);

В рассмотренном примере указатель на функцию funPtr описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция, описанная иначе, чем указатель, произойдет ошибка.

Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).

Пример:

float proiz(float x,float dx,float(*f)(float x)); float fun(float z);

int main()

 

 

 

{float x;

/* точка вычисления производной */

float dx;

/*

приращение

*/

float z;

/*

значение производной

*/

scanf("%f,%f",&x,&dx);

/* ввод значений x и dx

*/

z=proiz(x,dx,fun);

/*

вызов функции

*/

printf("%f",z);

/*

печать значения производной

*/

}

 

 

 

float proiz(float x,float dx,float (*f)(float z))/* функция вычисляющая производную */

{float xk,xk1; xk=fun(x); xk1=fun(x+dx);

return (xk1/xk-1e0)*xk/dx;

}

float fun( float z) /* функция от которой вычисляется производная */

{return (cos(z));

}

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой

63

функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме:

z=proiz(x,dx,cos);

а для вычисления производной от функции sin(x) в форме

z=proiz(x,dx,sin);

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

В стандартном языке программирования C все функции могут быть вызваны сами из себя или использоваться различными программными потоками одновременно. Для этого все локальные переменные располагаются в стеке. В микроконтроллерах семейства MCS-51 ресурсы внутренней памяти данных ограничены, поэтому в языке программирования С-51 для функций локальные переменные по умолчанию располагаются не в стеке, а непосредственно во внутренней памяти микроконтроллера. Если же подпрограмма должна вызываться рекурсивно, то ее необходимо объявить как программу с повторным вы-

зовом (reentrant):

return_type funcname ([args]) reentrant

Классический пример рекурсии – это математическое определение факториала n!:

n! = 1

при

n=0;

n*(n-1)!

при

n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

long fakt(int n) reentrant {return ((n==1)?1:n*fakt(n-1));

}

Подпрограммы обработки прерываний.

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

interrupt N;

где N-любое десятичное число от 0 до 31.

64

Число N определяет номер обрабатываемого прерывания. При этом номер 0 соответствует внешнему прерыванию от ножки INT0, номер 1 соответствует прерыванию от таймера 0, номер 2 соответствует внешнему прерыванию от ножки INT1 и так далее. Пример подпрограммы-обработчика прерывания от таймера 0:

void IntTim0(void) interrupt 1

{TH0=25; TL0=32; /*Задать новый интервал времени таймера T0

*/

TF=0; /*Сбросить флаг таймера T0 для разрешения следующего прерывания

от данного таймера

*/

}

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

void IntTim0(void) interrupt 2 using 1

{TH0=25; TL0=32; /*Задать новый интервал времени таймера T0

*/

TF=0; /*Сбросить флаг таймера T0 для разрешения следующего прерывания

от данного таймера

*/

}

1.15. Области действия переменных и подпрограмм

Любой объект, который объявляется в программе, написанной на языке программирования C-51, имеет область действия. В языке программирования C область действия объекта распространяется на всю программу, то есть любой объект является глобальным.

К объектам запрещено обращение до того, как они будут объявлены, поэтому при обращении к объекту, объявленному в другом программном модуле этот объект должен быть объявлен в данном программном модуле как external. Например:

extern char code ERROR [];

/*Строка сообщения об ошибке */

extern struct mrec current;

/* Текущее измерение */

extern struct interval setinterval;

/* Значения установленных интервалов */

extern struct interval counter ;

/* Счетчик интервалов */

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

static.

65

Например:

static int i; //Глобальная переменная, недоступная из других модулей //(а значит и не мешающая им)

void tmp(void) {i=5;

}

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

Локальные переменные существуют только в пределах структурного оператора. Если внутри структурного оператора были присвоены локальным переменным какие-то значения, то не следует ожидать, что вернувшись снова в этот оператор, переменные сохранят присвоенные ранее или вычисленные значения.

Пример 1:

Пример 2:

66

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

Например:

void tmp(void) {static int i=5;

}

То же самое относится и к подпрограммам. Если требуется объявить подпрограмму, которая не должна быть видна из других модулей, то ее необходимо объявить с атрибутом static. Это кроме прочего приведет к тому, что если эта подпрограмма не используется и в данном модуле, то она просто не будет транслироваться и занимать место в памяти программ. Пример объявления подпрограммы с атрибутом static:

static void tmp(void) {i=5;

}

Иногда требуется локальные переменные хранить не в одних и тех же ячейках памяти, а в стеке. Это требуется, например, при вызове подпрограммы самой из себя. В таком случае подпрограмма должна быть объявлена как reentrant.

Теперь, после краткого знакомства с языком программирования С-51, давайте познакомимся с процессом написания программ. Как уже мы упоминали в начале книги, программы пишутся в интегрированной среде программирования. Знакомству с такой средой программирования и будет посвящена следующая часть книги.

2.Работа с интегрированной средой программирования

2.1.Написание программы

Внастоящее время программы пишутся на одном из языков программирования в виде текстовых файлов. Это означает, что для написания программы можно воспользоваться любым текстовым редактором. Для того чтобы про- грамма-транслятор могла преобразовать исходный текст программы в машинные коды микропроцессора, этот текст программы должен быть записан с использованием символов ASCII или ANSI таблиц. К сожалению, некоторые текстовые редакторы для увеличения возможностей редактирования и отображения текста используют для его записи формат rtf или свои собственные форматы (например, текстовый редактор WORD). Такие текстовые файлы не понимаются программами-трансляторами и, следовательно, не могут быть использованы для записи исходного текста программы.

Для облегчения процесса разработки программы часто используются интегрированные среды программирования. В состав интегрированной среды про-

67

граммирования включается определенный набор программных средств: редактор исходного текста, трансляторы с выбранного языка программирования, редакторы связей, загрузчики, отладчики и так далее.

Редактор текстов обычно является первой программой, которую приходится использовать в процессе разработки программ: благодаря нему программист получает возможность набирать исходные тексты программ, написанных на ассемблере или на одном из языков высокого уровня и сохранять их на жёстком диске.

2.2. Работа с текстовым редактором интегрированной среды программирования keil-С

Работа с текстовыми файлами начинается с создания нового файла. Создать текстовый файл можно несколькими способами. Первый способ – воспользоваться главным меню, как показано на рисунке 6.

Рисунок 6. Создание нового файла через главное меню

Второй способ – использовать быстрые клавиши Ctrl+N. И третий способ – это нажать на пиктограмму создания нового файла, как показано на рисунке 7.

После выполнения этих действий открывается окно текстового редактора, в котором можно вводить исходный текст программы. Внешний вид программы с открытым окном текстового редактора показан на рисунке 8. Ввод программы производится с клавиатуры. Стирание одиночных ошибочно введённых символов возможно при помощи кнопок “Delete” и “Backspace”

Пиктограмма создания нового файла

Рисунок 7. Создание нового файла при помощи пиктограммы

68

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

Меню сохранения файла

Рисунок 8. Сохранение файла через главное меню

Второй способ – использовать быстрые клавиши Ctrl+S. И третий способ – это нажать на пиктограмму сохранения файла, как показано на рисунке 9.

При наборе исходного текста программы часто требуется копировать участки программ из одного файла в другой. Для этого в текстовом редакторе открываются оба файла. Затем необходимый участок текста выделяется при помощи мыши или клавиатуры. Для выделения строк нажимается левая кнопка мыши в начале выделяемого фрагмента и, не отпуская её, курсор мыши перемещается в конец этого фрагмента. Для выделения столбцов производятся те же действия, но, кроме того, нажимается кнопка “Alt” на клавиатуре.

Пиктограмма сохранения файла

Рисунок 9. Сохранение файла при помощи пиктограммы

После выделения необходимого фрагмента текста, этот фрагмент копируется в буфер обмена. Скопировать можно, щёлкнув мышью по пиктограмме копирования, как это показано на рисунке 10.

Теперь можно переключиться в окно редактирования файла, куда нужно поместить скопированный текстовый фрагмент при помощи главного меню, как это показано на рисунке 11, и вставить этот фрагмент перед текстовым курсором.

69

Копировать

Рисунок 10. Копирование выделенного фрагмента в буфер обмена при помощи пиктограммы

Вставка фрагмента из буфера обмена может быть произведена либо из меню “Edit”, либо при помощи пиктограммы “Paste” как это показано на рисунке 12. Фрагмент исходного текста будет вставлен в то место набираемого текста, где в настоящее время находится курсор.

Выбор окна редактирования файла

Рисунок 11. Выбор окна редактирования файла

Вставка скопированного фрагмента из буфера обмена

Рисунок 12. Вставка скопированного фрагмента из буфера обмена при помощи пиктограммы

Напомним, что копирование участка исходного текста программы эквивалентно набору этого текста программы с клавиатуры и поэтому файл, в который производилось копирование текста, необходимо сохранить на диске любым из методов, описанных ранее.

70