Використання підпрограм в мові програмування С-51*.
На відміну від інших мов програмування високого рівня в мові С немає ділення на основну програму процедуры и функции. У цій мові вся програма будується тільки з функцій. Потужність мови С багато в чому визначається легкістю і гнучкістю оголошення і реалізації функцій.
Функція - це сукупність оголошень і операторів, зазвичай призначена для вирішення певного завдання. Термін функція в мові програмування С еквівалентний поняттю подпрограммы. Дії, що виконуються основною програмою в інших мовах програмування такі як очищення внутрішнього ОЗУ і привласнення початкового значення змінним, виконуються автоматично при включенні живлення. Після завершення цих дій викликається підпрограма з ім'ям main. Кожна функція повинна мати ім'я, яке використовується для її оголошення, визначення і виклику.
У будь-якій програмі, написаній на мові програмування С-51 повинна бути функція з ім'ям main (головна функція), саме з цієї функції, в якому б місці програми вона не знаходилася, починається виконання програми. Звернете увагу, що на мові програмування С-51 пишуться програми для мікроконтролерів. Тому ці програми не повинні завершуватися поки включено живлення мікроконтролера. Це означає, що у функції main обов'язково повинен бути нескінченний цикл. Інакше при виході з цієї функції управління буде передано випадковій адресі пам'яті програм. Це може привести до непередбачуваних результатів, аж до виходу мікроконтроллерной системи з ладу.
При виклику функції їй за допомогою аргументів (формальних параметрів) можуть бути передані деякі значення (фактичні параметри), використовувані під час виконання функції. Функція може повертати деяке, але тільки одне, значення. Це значення, що повертається, і є результат виконання функції, який при виконанні програми підставляється у вираз, з якого проводився виклик функції.
Допускається також використовувати функції що не мають аргументів і функції що не повертають ніяких значень. Дія таких функцій може полягати, наприклад, в зміні значень деяких змінних, висновку на екран рідкокристалічного індикатора текстових написів і т.п.. Іншими словами можна сказати, що така функція працює подібно до підпрограми-процедури.
З використанням функцій в мові С-51 зв'язано три поняття - визначення функції (опис дій, що виконуються підпрограмою-функцією), оголошення функції (завдання форми звернення до функції) і виклик функції.
Визначення підпрограм
Визначення функції складається із заголовка і тіла. Визначення функції записується в наступному вигляді:
[спецификатор класса памяти] [спецификатор типа] имя функции ([список формальных параметров]) //Заголовок функции{ //тело функции }Заголовок функції задає тип значення, що повертається, ім'я функції, типи і число формальних параметрів.
Тіло функції - це складений оператор, що містить операторів, що визначають дію функції. Тіло функції починається з фігурної дужки '{' і складається з оголошення змінних і виконуваних операторів. Саме ці оператори, що входять в тіло функції, і визначають дію функції. Завершується тіло функції закриваючою фігурною дужкою '}'.
Приклад визначення функції:
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 видає попередження про це. Ця властивість може бути корисною при відладці програм і програмних модулів.
Використання специфікатора класу пам'яті 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; //то занести в буфер передатчика последовательного порта байт 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. Наприклад:
void main(void) {P0=0; //Зажигание светодиода while(1); //Бесконечный цикл}Порядок і типи формальных параметров повинні бути однаковими у визначенні функції і у всіх її оголошеннях. Тому бажане оголошення функції помістити в окремий файл, який потім можна включити в початкові тексти програмних модулів за допомогою директиви #include. Типи фактичних параметрів при виклику функції повинні бути сумісні з типами відповідних формальних параметрів. Тип формального параметра може бути будь-яким основним типом структурой, объединением, перечислением, указателем или массивом. Якщо тип формального параметра не вказаний, то цьому параметру привласнюється тип int.
Для формального параметра можна задавати клас пам'яті register, при цьому для величин типу int специфікатор типу можна опустити. Проте всі відомі мені компілятори мови програмування С-51 ігнорують цей специфікатор, оскільки розташування параметрів в пам'яті мікроконтролера оптимізується з погляду використання мінімальної кількості необхідної внутрішньої пам'яті.
За умовчанням компілятори прагнуть передавати параметри у функцію через регістри, тим самим максимально зберігаючи внутрішню пам'ять мікроконтролерів. Номери регістрів, використовувані для передачі параметрів у функцію залежно від типу аргументів приведені в таблиці 1
Таблиця 1
Номер аргумента | char, однобайтовый указатель | int, двухбайтовый указатель | long,float | Нетипизированные указатели |
R7 | R6,R7 | R4 - R7 | R1 - R3 | |
R5 | R4,R5 | R4 - R7 | R1 - R3 | |
R3 | R2,R3 | R1 - R3 |
Оскільки при виклику функції значення фактичних параметрів копіюються в локальні змінні, в тілі функції не можна змінити значення змінних в зухвалій функції. Наприклад потрібно поміняти місцями значення змінних x і у:
/* Неправильное использование параметров функции */void change (int x, int y){int k=x; x=y; y=k;}У даній функції значення локальних змінних x і у, що є формальними параметрами, міняються місцями, але оскільки ці змінні існують тільки усередині функції change, значення фактичних параметрів, використовуваних при виклику функції, залишаться незмінними.
Проте, якщо як параметр функції використовувати покажчик змінну, то можна змінити значення змінної, адреса якої міститиметься в покажчику. Для того, щоб мінялися місцями значення фактичних аргументів можна використовувати функцію приведену в наступному прикладі:
/* Правильное использование параметров функции */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);При цьому в оголошенні функції імена формальних параметрів можуть бути опущені:
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. Сама функція повинна повертати значення типу int. Круглі дужки, що містять ім'я покажчика fun і ознака покажчика *, обов'язкові, інакше запис
int *fun (int x,int *y);
інтерпретуватиметься як оголошення функції fun що повертає покажчик на 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 ім'я іншої функції. Зокрема, для обчислення похідної від функції cos(x) можна викликати функцію proiz у формі
z=proiz(x,dx,cos);
а для обчислення похідної від функції sin(x) у формі
z=proiz(x,dx,sin);
Рекурсивний виклик підпрограм.
У стандартній мові програмування Зі всі функції можуть бути викликані самі з себе або використовуватися різними програмними потоками одночасно. Для цього всі локальні змінні розташовуються в стеке. У мікроконтролерах сімейства 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.
Число 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 для разрешения следующего прерывания от данного таймера}
Дата добавления: 2017-01-29; просмотров: 1001;