Составление проекта из нескольких файлов исходниковРано или поздно возникает ситуация, когда разработчик завершает один проект и приступает к написанию другого. Очевидно, что накопленные предыдущие знания (точнее говоря – куски программного кода) могут быть скопированы в новый проект. Однако, мы с вами ранее создавали всего лишь один файл (исходник на Си) в который всё сваливали в кучу и по мере желания пытались комментировать.
Сейчас мы с вами научимся создавать несколько файлов исходников которые будут работать в одном проекте. Зачем это надо? Несколько причин:
1) в разных файлах удобнее группировать функции по решению определенных задач (индикация, опрос клавиатуры, логика работы устройства, работа с EEPROM, управление другими устройствами, протоколы I2C, 1-Wire, RC5 и т.п.);
2) с каждым файлом визуальнее проще и нагляднее работать да и код не упирается «простынёй» в пол;
3) для будущих проектов элементарно подключить необходимые файлы и использовать готовые функции, что экономит время.
Итак, с помощью визарда мы создали проект. Затем создали рыбу
#include <pic.h>
__CONFIG (INTIO & UNPROTECT & LVPDIS & BOREN
& MCLRDIS & PWRTEN & WDTDIS);
void main (void)
{
}
и успешно откомпилировали.
Как создать новый файл исходника и подключить к проекту? Да точно также как и создать новую рыбу, только без текста (всё то же самое):
Нажимаем меню File – New. Появляется окно с именем Untitled. Нажимаем меню File – Save As… и приходим в путь с нашим проектом. Далее вводим имя файла (например, proba.c) и не забываем поставить галочку Add File To Project (Добавить Файл В Проект) + Сохранить. Всё.
А представьте ситуацию, когда вы уже имеете файл исходник с функциями по обслуживанию какого-то процесса, например, idikator.c. Здесь вам нужно вспомнить случай, когда вы забыли поставить галочку. Правой кнопкой мыши в окне дерева проекта щелкаем на ветке Source File (англ. – источники; сленг – сырцы) и выбираем пункт Add Files…, затем находим и открываем наш файл idikator.c . В дереве появляется наш файл. Это признак того, что файл подключен к проекту. Ура.
С чего начинается не «главный» исходник (где отсутствует функция main)? Рассмотрим конкретный пример. Пусть наш проект состоит из двух исходников. В главном исходнике мы конфигурируем МК, пишем main функцию из который инициализируем порты в функции podgot, затем входим в бесконечный цикл, который вызывает функцию мигалки из второго исходника.
Пишем в главный файл
#include <pic.h>
__CONFIG (INTIO & UNPROTECT & LVPDIS & BOREN
& MCLRDIS & PWRTEN & WDTDIS);
// === объявляем функции
void podgot (void); // подготовка МК
extern void migalka (void); // внешняя функция мигалки
// === главная функция
void main (void){ // начало функции
podgot (); // вызываем функцию подготовки
while(1)
{ // начало бесконечного цикла
migalka (); // внешняя функция мигалки
} // конец бесконечного цикла
} // конец функции
// === подготовка МК
void podgot (void){ // начало функции
INTCON = 0; // чистим регистр прерываний
TRISA = 0b00000000; TRISB = 0b11111111; // направления
CMCON = 0x07; // отключение компараторов
PORTA = 0; PORTB = 0; // чистим порты
RBPU = 0; // подтягивающие R (1-откл, 0-вкл)
} // конец функции
пишем во второй файл
#include <pic.h>
// === сопоставление сигнальных линий
#define out1 RA6 // выход 1
#define out2 RA7 // выход 2
// === используемые функции
void pauza (void); // функция паузы
// === функция мигалки
void migalka (void){ // начало функции
out1 = 1; // установили ВЛУ
out2 = 0; // установили НЛУ
pauza (); // функция паузы
out1 = 1-out1; // инвертировать
out2 = 1-out2; // инвертировать
pauza (); // функция паузы
} // конец функции
// === функция паузы
void pauza (void){ // начало функции
unsigned int tmp; // локальная переменная
tmp = 0xffff; // в tmp поместить некое максимальное число
while (tmp-->0); /* выполнять декрементирование tmp
до тех пор, т.е. ПОКА tmp больше нуля */
}// конец функции
Компилим и смотрим в симуляторе. Работает.
По комментариям должно быть понятно, но мы акцентируем внимание. Для того, чтобы использовать функцию или глобальные переменные из одного файла исходника в другом файле исходнике необходимо представлять эти функции и переменные с помощью extern (внешний). Ну, например.
В одном файле у нас описано следующее:
void klava (void); // функция клавиатуры
bit knopka = 1; // инициализированный бит состояния кнопки
unsigned char rezim_n = 5; // инициализированный нумератор режима
unsigned char count; // переменная счетчика
Если в другом файле нам надо работать с этими функциями и переменными, то мы их обозначим (в другом файле) через extern
extern void klava (void); // внешняя функция клавиатуры
extern bit knopka; // внешний бит состояния кнопки
extern unsigned char rezim_n; // внешний нумератор режима
extern unsigned char count; // внешняя переменная счетчика
Что мы видим? Всё логично. Нельзя дважды инициализировать, т.е. присваивать некое значение переменным. Можно лишь использовать ранее инициализированное (или не инициализированное). С внешними переменными и функциями работаем также как и с обычными переменными, их только нужно представить через extern .
На что обратить внимание в примерах?
TRISA = 0b00000000; TRISB = 0b11111111; // направления
– бинарная запись нагляднее, сразу видно как работают линии.
out1 = 1-out1; // инвертировать
out2 = 1-out2; // инвертировать
– можно было тупо написать необходимое, но нужно знать способ инвертирования. Переменные типа bit, char, int и др. (bit не путать с линией) можно инвертировать как tmp = ~tmp; ,
ну или указанным способом tmp = 1 – tmp; .
unsigned int tmp; // локальная переменная
tmp = 0xffff; while (tmp-->0);
– простой способ организации задержки, где время будет составлять tmp*11 мкс (при частоте тактирования в 4 МГц), т.е. в нашем примере 65535*11 = 720885 мкс или 0,72 сек. Обращаю внимание, переменная tmp типа unsigned int (двухбайтное).
|