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

691_Mikushin_A.V._Programmirovanie_mikroprotsessorov_

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

stmt level source

1 char *c_ptr; /* char ptr */

2 int *i_ptr; /* int ptr */

3 long *l_ptr; /* long ptr */

4

5void main (void)

6{

71 char data dj; /*переменные во внутренней памяти данных data */

81 int data dk;

91 long data dl;

101

111 char xdata xj; /*переменные во внешней памяти данных xdata */

121 int xdata xk;

131 long xdata xl;

141

151 char code cj = 9; /*переменные в памяти программ code */

161 int code ck = 357;

171 long code cl = 123456789;

181

191 /*настроим указатели на внутреннюю память данных data */

201 c_ptr = &dj;

211 i_ptr = &dk;

221 l_ptr = &dl;

231 /*настроим указатели на внешнюю память данных xdata */

241 c_ptr = &xj;

251 i_ptr = &xk;

261 l_ptr = &xl;

271 /*настроим указатели на память программ code */

281 c_ptr = &cj;

291 i_ptr = &ck;

301 l_ptr = &cl;

311 }

ASSEMBLY LISTING OF GENERATED OBJECT CODE

;FUNCTION main (BEGIN)

;SOURCE LINE # 5

;SOURCE LINE # 6

;SOURCE LINE # 20

0000 750000

R

MOV

c_ptr,#00H

0003 750000

R

MOV

c_ptr+01H,#HIGH dj

0006 750000

R

MOV

c_ptr+02H,#LOW dj

 

 

 

; SOURCE LINE # 21

0009 750000

R

MOV

i_ptr,#00H

000C 750000

R

MOV

i_ptr+01H,#HIGH dk

000F 750000

R

MOV

i_ptr+02H,#LOW dk

 

 

 

; SOURCE LINE # 22

0012 750000

R

MOV

l_ptr,#00H

0015 750000

R

MOV

l_ptr+01H,#HIGH dl

0018 750000

R

MOV

l_ptr+02H,#LOW dl

 

 

 

; SOURCE LINE # 24

001B 750001

R

MOV

c_ptr,#01H

001E 750000

R

MOV

c_ptr+01H,#HIGH xj

0021 750000

R

MOV

c_ptr+02H,#LOW xj

 

 

 

; SOURCE LINE # 25

0024 750001

R

MOV

i_ptr,#01H

0027 750000

R

MOV

i_ptr+01H,#HIGH xk

002A 750000

R

MOV

i_ptr+02H,#LOW xk

 

 

 

; SOURCE LINE # 26

51

002D

750001

R

MOV

l_ptr,#01H

0030

750000

R

MOV

l_ptr+01H,#HIGH xl

0033

750000

R

MOV

l_ptr+02H,#LOW xl

 

 

 

 

; SOURCE LINE # 28

0036

7500FF R

MOV

c_ptr,#0FFH

0039

750000

R

MOV

c_ptr+01H,#HIGH cj

003C

750000

R

MOV

c_ptr+02H,#LOW cj

 

 

 

 

; SOURCE LINE # 29

003F

7500FF R

MOV

i_ptr,#0FFH

0042

750000

R

MOV

i_ptr+01H,#HIGH ck

0045

750000

R

MOV

i_ptr+02H,#LOW ck

 

 

 

 

; SOURCE LINE # 30

0048

7500FF R

MOV

l_ptr,#0FFH

004B

750000

R

MOV

l_ptr+01H,#HIGH cl

004E

750000

R

MOV

l_ptr+02H,#LOW cl

 

 

 

 

; SOURCE LINE # 31

0051

22

 

RET

 

 

; FUNCTION main (END)

Память зависимые указатели.

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

char data *str;

/*указатель на строку во внутренней памяти данных data */

int xdata *numtab;

/*указатель на целую во внешней памяти данных xdata */

long code *powtab;

/*указатель на длинную целую в памяти программ code */

Поскольку модель памяти определяется во время компиляции, типизированным указателям не нужен байт, в котором указывается тип памяти микроконтроллера. Поэтому программа с использованием типизированных указателей будет короче, и будет выполняться быстрее по сравнению с программой, использующей нетипизированные указатели. Типизированные указатели могут иметь размер в 1 байт (указатели на память idata, data, bdata, и pdata) или в 2 байта (указатели на память code и xdata).

1.12.Объявление новых типов переменных

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

Объявить новый тип переменной можно двумя способами. Первый способ

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

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

52

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

Примеры объявления и использования новых типов:

typedef

float (* MATH)( ); // MATH - новое имя типа, представляющее указатель на функцию, //возвращающую значения типа float

typedef

 

char FIO[40]

// FIO - массив из сорока символов

MATH cos;

// cos указатель на функцию, возвращающую значения

типа double

 

// Можно провести эквивалентное объявление

float (* cos)( );

FIO person;

//Переменная person - массив из сорока символов

//Это эквивалентно объявлению char person[40];

При объявлении переменных и типов здесь были использованы имена типов MATH и FIO. Помимо объявления переменных, имена типов могут еще использоваться в трех случаях: в списке формальных параметров при объявлении функций, в операциях приведения типов и в операции sizeof .

1.13.Инициализация данных

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

main();.

Инициатор переменной начинается со знака "=" и может быть записан в следующих форматах:

Формат 1: = инициатор; Формат 2: = { список - инициаторов };

Формат 1 используется при инициализации переменных основных типов и указателей, а формат 2 – при инициализации составных объектов.

Примеры присваивания первоначальных значений простым переменным:

53

char tol =

'N';

//Переменная tol инициализируется символом 'N'.

const long

megabyte = (1024*1024);

Немодифицируемой переменной megabyte присваивается значение константного выражения, после чего эта переменная не может быть изменена. Отмечу, что для микроконтроллеров семейства MCS-51 внутренняя память является дефицитным ресурсом, поэтому использовать ее для хранения констант нерационально. Лучше объявить переменную со спецификатором типа памяти

code.

static int b[2][2] = {1,2,3,4};

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

static int b[2][2] = { { 1,2 }, { 3,4 } };

При инициализации массива можно опустить одну или несколько размерностей:

static int

b[3] = { { 1,2 }, { 3,4 } };

Если при инициализации указано меньше значений для строк, то оставшиеся элементы инициализируются 0, т.е. при описании:

static int b[2][2] = { { 1,2 }, { 3 } };

элементы первой строки получат значения 1 и 2, а второй 3 и 0.

При инициализации составных объектов, нужно внимательно следить за использованием скобок и списков инициализаторов.

Примеры:

struct complex {float real; float imag;

}comp[2][3]={{{1,1},{2,3},{ 4, 5}}, {{6,7},{8,9},{10,11}} };

Вданном примере инициализируется массив структур comp из двух строк

итрех столбцов, где каждая структура состоит из двух элементов real и imag.

struct

complex comp2 [2][3] = { {1,1},{2,3},{4,5},{6,7},{8,9},{10,11} };

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

54

1.первая левая фигурная скобка – начало составного инициатора для мас-

сива comp2;

2.вторая левая фигурная скобка – начало инициализации первой строки массива comp2[0]. Значения 1,1 присваиваются двум элементам первой структуры;

3.первая правая скобка (после 1) указывает компилятору, что список инициаторов для строки массива окончен, и элементы оставшихся структур в строке comp[0] автоматически инициализируются нулем;

4.аналогично список {2,3} инициализирует первую структуру в строке comp[1], а оставшиеся структуры массива обращаются в нули;

5.на следующий список инициализаторов {4,5} компилятор будет сообщать о возможной ошибке, так как строка 3 в массиве comp2 отсутствует.

При инициализации объединения задается значение первого элемента объединения в соответствии с его типом.

Пример инициализации объединения:

union tab

{unsigned char name[10]; int tab1;

}pers={'A','H','T','O','H'};

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

Инициализацию массива символов можно выполнить при помощи литеральной строки.

char stroka[ ] = "привет";

Инициализируется массив символов из 7 элементов, последним элементом (седьмым) будет символ '\0', которым завершаются все литеральные строки.

В случае если задается размер массива, а литеральная строка длиннее, чем размер массива, то лишние символы отбрасываются. Следующее объявление инициализирует переменную stroka как массив, состоящий из семи элементов.

char stroka[5]="привет";

В переменную stroka попадают первые пять элементов литерала, а символы 'т' и '\0' отбрасываются. Если строка короче размерности массива, то оставшиеся элементы массива заполняются нулями. Отметим, что инициализация переменной типа tab может иметь следующий вид:

union tab pers1="Антон";

и, таким образом, в символьный массив попадут символы:

55

'А','Н','Т','О','Н','\0',

ав остальные элементы будут записаны нули.

1.14.Использование функций в языке программирования С-51

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

Функция – это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Термин функция в языке программирования C эквивалентен понятию подпрограммы. Действия, выполняемые основной программой в других языках программирования, такие как очистка внутреннего ОЗУ и присваивание начального значения переменным, выполняются автоматически при включении питания. После завершения этих действий вызывается подпрограмма с именем main. Каждая функция должна иметь имя, которое используется для ее объявления, определения и вызова.

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

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

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

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

56

Определение функций.

Определение функции состоит из заголовка и тела. Определение функции записывается в следующем виде:

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

{

//тело функции

}

Заголовок функции задает тип возвращаемого значения, имя функции, типы и число формальных параметров.

Тело функции – это составной оператор, содержащий операторы, определяющие действие функции. Тело функции начинается с фигурной скобки '{' и состоит из объявления переменных и исполняемых операторов. Именно эти операторы, входящие в тело функции, и определяют действие функции. Завершается тело функции закрывающей фигурной скобкой '}'.

Пример определения функции:

bit SostKnIzm(void)//Заголовок функции

{//---------------начало тела функции--------------

bit tmp=0; if(P0!=0xff)tmp=1;

return tmp;

 

}//---------------

конец тела функции-----------------

//================== Вызывающая подпрограмма ==========

...

if(SostKnIzm()) //Вызов подпрограммы SostKnIzm

DecodSostKn();

В приведенном примере показано как при помощи функции, возвращающей битовую переменную можно повысить наглядность исходного текста программы. Оператор if(SostKnIzm()) DecodSostKn(); практически не требует комментариев. Имя функции SostKnIzm показывает, что контролирует эта функция.

Необязательный спецификатор класса памяти задает класс памяти функции, который может быть static или extern.

При использовании спецификатора класса памяти static функция становится невидимой из других файлов программного проекта, то есть информация об этой функции не помещается в объектный файл. Использование спецификатора класса памяти static может быть полезно для того, чтобы имя этой функции могло быть использовано в других файлах программного проекта для реализации совершенно других задач. Если функция, объявленная с спецификатором класса памяти static, ни разу не вызывалась в данном файле, то она вообще не транслируется компилятором и не занимает места в программной памяти микроконтроллера, а программа-компилятор языка программирования С-51 выдает предупреждение об этом. Это свойство может быть полезным при отладке программ и программных модулей.

57

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

Спецификатор типа функции задает тип возвращаемого значения и может задавать любой тип. Если спецификатор типа не задан, то по умолчанию предполагается, что функция возвращает числовое значение типа int. Функция не может возвращать массив или функцию, но это ограничение можно обойти, так как она может возвращать указатель на любой тип, в том числе и на массив и на функцию. Тип возвращаемого значения, задаваемый в определении функции, должен соответствовать типу в объявлении этой функции.

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

#include <reg51.h>//Подключить описания внутренних регистров микроконтроллера

char getkey ()

//Заголовок функции, возвращающей байт, принятый по

последовательному

порту

{while (!RI);

//Если последовательный порт принял байт,

RI = 0;

//то подготовиться к приёму следующего байта

return (SBUF);

//и передать принятый байт в вызывающую подпрограмму.

}

 

В операторе return возвращаемое значение может записываться как в скобках, так и без них. Если функция определена как функция, возвращающая некоторое значение (подпрограмма-функция), а в операторе return при выходе из нее отсутствует выражение, то это может привести к непредсказуемым результатам.

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

void putchar (char c) //Заголовок функции, передающей один байт через последовательный порт

{while (!TI); //Если передатчик последовательного порта готов к передаче байта

SBUF = c; //то занести в буфер передатчика последовательного порта байт

58

void.

TI = 0;

//и начать передачу

}

 

Все переменные, объявленные в теле функции без указания класса памяти, имеют класс памяти auto, т.е. они являются локальными. Так как глубина стека в процессорах семейства MCS-51 ограничена 256 байтами, то при вызове функций аргументам назначаются конкретные адреса во внутренней памяти микроконтроллера и производится их инициализация. Управление передается первому оператору тела функции и начинается выполнение функции, которое продолжается до тех пор, пока не встретится оператор return или последний оператор тела функции. Управление при этом возвращается в точку, следующую за точкой вызова, а локальные переменные становятся недоступными. При выходе из функции значения этих переменных теряются, так как при вызове других функций эти же ячейки памяти распределяются для их локальных переменных.

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

Параметры функций.

Список формальных параметров – это последовательность объявлений формальных параметров, разделенная запятыми. Формальные параметры – это переменные, используемые внутри тела функции и получающие значение при вызове функции путем копирования в них значений соответствующих фактических параметров.

Пример определения функции с одним параметром:

int

rus (unsigned char r) //Заголовок функции

{//---------------

начало тела функции--------------

if (r>='А' && c<=' ')

 

return 1;

 

else

 

return 0;

}//---------------

конец тела функции-----------------

В данном примере определена функция с именем rus, имеющая один параметр с именем r и типом unsigned char. Функция возвращает целое значение, равное 1, если параметр функции является буквой русского алфавита, или 0 в противном случае.

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

void main(void)

{P0=0; //Зажигание светодиода while(1); //Бесконечный цикл

}

Порядок и типы формальных параметров должны быть одинаковыми в определении функции и во всех ее объявлениях. Поэтому желательно объявле-

59

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

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

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

 

 

 

 

Таблица 8

Номер

char, однобайто-

int, двухбайто-

long,

Нетипизированные

аргумента

вый указатель

вый указатель

float

указатели

1

R7

R6,R7

R4 - R7

R1 - R3

2

R5

R4,R5

R4 - R7

R1 - R3

3

R3

R2,R3

 

R1 - R3

Поскольку при вызове функции значения фактических параметров копируются в локальные переменные, в теле функции нельзя изменить значения переменных в вызывающей функции. Например, нужно поменять местами значения переменных x и y:

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

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

}

В данной функции значения локальных переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными.

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

60