Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Как правильно создать многофайловый проект
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему
alux
Привет всем.
Создаю проект на IAR v.4.12A, состоящий из многих С-файлов. Проблема возникает при линковке в один файл. Если быть точнее, то при попытке вызова battery_charge(); из main возникает ошибка Error[e46]: Undefined external "FAST_charge" referred in bc ( IAR projects\pribor\Release\Obj\bc.r90 ). В main подключены хидеры всех используемых модулей, в том числе и bc.h :

///// main.c ////////
#include "bc.h"
...........
void main (void)
{
battery_charge();
}

//// bc.c /////
#include "bc.h"

void battery_charge(void)
{
FAST_charge();
}

///// bc.h ////////
extern void FAST_charge(void);
extern void TRICKLE_charge(void);
extern void battery_charge(void);

//// NiMh.c ////////
#include "NiMH.h"
void FAST_charge (void)
{
}


//// NiMh.h //////
void FAST_charge(void);

Помогите найти ошибку, пожалуйста. И расскажите популярно как создавать многофайловые проекты, а то все приходится делать методом научого тыка.
jorikdima
хидеры то подключены, а в опциях проекта пути к ним прописаны? (если они по поддиректориям распределены)
zltigo
Цитата(alux @ May 6 2007, 17:07) *
void battery_charge(void)
{
FAST_charge();
}

Описка. Найдите разницу в одной букве:
Цитата
void FAST_carge (void)
{
}


И пожалуй не стоит писать в AVR->IAR, ибо сие ни к AVR ни к IAR отнрошения не имеет.

P.S.
И дублировать прототипы функций в headers это лишнее.
alux
Цитата(jorikdima @ May 6 2007, 17:20) *
хидеры то подключены, а в опциях проекта пути к ним прописаны? (если они по поддиректориям распределены)

Все файлы (*.с, *.h) лежат в одной рабочей папке.


Цитата(zltigo @ May 6 2007, 17:24) *
И дублировать прототипы функций в headers это лишнее.

Ошибся при наборе сообщения, уже исправил...
rezident
Цитата(alux @ May 6 2007, 20:46) *
Все файлы (*.с, *.h) лежат в одной рабочей папке.

Дык мало положить их в одну папку, нужно их еще и в проект включить. См. пример на скриншоте.
P.S. кстати, не обязательно все файлы валить в одну кучу. Я для хидеров каждого проекта использую каталог _INC, а для Си-ных исходников каталог _LIB. А в самих файлах пишу относительные пути (на скриншоте выделено красным). Таким образом я могу весь проект легко перенести на другой диск или в другой каталог без потери связей между файлами и перенастройки проекта.
SasaVitebsk
Простите, решил сюда добавить, чтобы темы не плодить.
Не так давно пишу на Си. За этот период сильно изменил свои подходы. Теперь проект разбиваю на файлы, которые логически закончены. Это даёт возможность многократного использования с минимальными изменениями. Но вот столкнулся с некоторыми проблемами в таком подходе. В принципе я о них уже читал в разных темах и общие указания слышал/запомнил.

Сама проблема возникает когда п/п из другого файла используется в прерывании, которое в main описывается. Как выходить из неё - вроде понятно. 1) Вообще убрать вызовы п/п. 2) перенести п/п в файл, где пишется прерывание.

Собственно меня интересует более общая тема. Как всётаки сделать так, чтобы и овцы целы и волки сыты. То есть вопросы следующие.

1) Правильным ли является подход с выделением логически законченного блока в отдельный файл со своим хедером. И если "нет" или "не совсем", то хотелось бы услышать как поступают проффессионалы.
2) Как использовать такие "почти библиотеки", чтобы уменьшить накладные рассходы и при этом сохранить красоту написания проги.
3) Является ли выигрышным в этом смысле вариант с С++ и созданием классов/объектов.
singlskv
Цитата(SasaVitebsk @ Dec 9 2007, 22:06) *
Сама проблема возникает когда п/п из другого файла используется в прерывании, которое в main описывается. Как выходить из неё - вроде понятно. 1) Вообще убрать вызовы п/п. 2) перенести п/п в файл, где пишется прерывание.
3) объявить п/п через extern в соответствующем хидере.
Цитата
1) Правильным ли является подход с выделением логически законченного блока в отдельный файл со своим хедером. И если "нет" или "не совсем"
Правильным, тока для разрешения всех противоречий нужно еще иметь "главный" хидер
в котором и объявляются основные настройки проекта и инклудятся все необходимые хидеры, ИМХО.
Сергей Борщ
Цитата(SasaVitebsk @ Dec 9 2007, 21:06) *
п/п из другого файла используется в прерывании, которое в main описывается.
Это как? Обычно обработчик прерывания описываю не в main, а в том же файле, что и остальные п/п соответстующего модуля. А зачем обработчик уносить в тот же файл, где и main? Или это архитектера вроде pic, когда одно прервание на все случаи? Плюсы помогут, ибо в них можно в заголовочном файле описать обработчик как inline.
Непомнящий Евгений
Просветите плиз - что такое "п/п"?
Николай Z
Цитата(Непомнящий Евгений @ Dec 10 2007, 09:26) *
Просветите плиз - что такое "п/п"?


Видимо подпрограмма.... Она же - функция...
Немного не С-шная терминология, но суть практически та же...
В Фортране к примеру - были функции, кторые возврвщали значение и void-функции, которые не возвращали значение или что почти то же самое обычно - возвращали пустое значение...

Еще в некоторых других языках использовалась такая терминология.
Непомнящий Евгений
Что такое подпрограмма - я в курсе smile.gif Просто не сообразил, что п\п - это ее сокращение. Спасибо.

Цитата(Сергей Борщ @ Dec 10 2007, 00:56) *
Это как? Обычно обработчик прерывания описываю не в main, а в том же файле, что и остальные п/п соответстующего модуля. А зачем обработчик уносить в тот же файл, где и main?

Ну например, чтобы не зависеть от конкретного прерывания. Тогда в модуле (а точнее в хидере) я пишу inline-функцию, а само прерывание делаю в основной программе, дергая из его обработчика эту функцию. Вместо inline-функции в С можно использовать макрос.
alexander55
Цитата(Сергей Борщ @ Dec 10 2007, 00:56) *
А зачем обработчик уносить в тот же файл, где и main?

Это дело вкуса.
Я вижу это так.
Если железно ясно, что
- используется прерывание
- и это универсальное решение,
то прерывание имеет смысл иметь в модуле, где осуществляется реализация.
Если :
- хочется иметь main как диспетчер, в котором видна вся логика функционирования
- main небольшой по размеру
- возможны изменения (без прерывания или с прерыванием),
то лучше обработчик прерывания в main.
В дальнейшем, когда все устаканится или размер main станет неприличным по размеру, можно его перенести. biggrin.gif
Сергей Борщ
Цитата(alexander55 @ Dec 11 2007, 08:17) *
Если :
- хочется иметь main как диспетчер, в котором видна вся логика функционирования
Если это ARM, то в main достаточно заносить адреса обработчиков (объявленных как extern) в нужные регистры контроллера прерываний. Тела самих обработчиков в main() не нужны. Если это проц без контроллера, то у него скорее всего вектора жестко распределены за периферией, и выбор вектора просто однозначен - в main нечего диспетчерить. Если хочется иметь выбор из нескольких одинаковых периферийных модулей (UART, например), то можно попробовать так:
Код
в Hardware.h указываем
#define RS232_MODULE   USART1
#define RS485_MODULE   USART0

в RS232.c:
#pragma RS232_MODULE##_UDRE_vect
interrupt void RS232_transmit_handler()
{

} //опять main.c не у дел
Наконец, если используются плюсы и обработчик - метод класса, то код этого метода с атрибутом inline размещается в заголовочном файле, а в main.cpp пишется "обертка":
Код
__irq void RS232_Handler()
{
      RS232.Handler();
}
или
#pragma USART1_UDRE_vect
interrupt void RS232_transmit_handler()
{
      RS232.Handler();
}
при этом обработчик делается private, а функция-обертка объявляется другом класса. Но при желании эти обертки уносятся в файл соответствующего модуля и можно снова применить первый вариант.
Цитата(alexander55 @ Dec 11 2007, 08:17) *
В дальнейшем, когда все устаканится или размер main станет неприличным по размеру, можно его перенести.
Мне кажется, что всякие методики проектирования создают именно для того, чтобы по возможности исключить такую непроизводительную работу. Хотя, конечно, идеала не существует и процесс был и будет итеративным. Но количество итераций можно попытаться уменьшить.
alexander55
Цитата(Сергей Борщ @ Dec 11 2007, 12:51) *
Наконец, если используются плюсы и обработчик - метод класса, то код этого метода с атрибутом inline размещается в заголовочном файле, а в main.cpp пишется "обертка":
Код
__irq void RS232_Handler()
{
      RS232.Handler();
}
или
#pragma USART1_UDRE_vect
interrupt void RS232_transmit_handler()
{
      RS232.Handler();
}

Я это и имел ввиду.

Цитата(Сергей Борщ @ Dec 11 2007, 12:51) *
в main нечего диспетчерить.

Это не совсем диспетчеризация и даже может совсем не диспетчерезация.
Приятно сразу понять логику работы всей системы глядя на один модуль.
Детали находятся за рамками и если требуются, то понятно, где это можно посмотреть (глядя на модуль).
Второй смысл - это скажем так "просмотр синхронизации потоков и распределенных приложений".
Во выдал. biggrin.gif
SasaVitebsk
Я, собственно так и работаю. И думал так все. smile.gif В смысле "main" главная или основная.

В main.h описываю основные переменные проекта. Например частоту проца и прочее. Этот хидер включаю во все хидера составных частей проекта (В данном последнем это Kls3x4.h, timerhw.h, lcd44780.h, wake.h). В каждом из составляющих хидеров определяются внутренние константы и объявления данного модуля и прототипы функций.

В самой main.c описывается вся программа опирающаяся на составные части. Прерывание описывается в main.c

Прерывание - метки времени. То есть там целый набор. smile.gif в том числе вызовы опроса клавиатуры. Вот и получается - засунуть её в Kls3x4.с некрасиво (там логически законченная библиотека) а при применении в main - есть накладные.


Собственно само по себе, естественно, это проблему не вызывает. Данное прерывание обрабатывается раз в 10мс и, честно говоря мне до лампочки что там весь контекст сохраняется. Я просто на будущее. Чтобы оценить кто как работает с оформлением и структурой программы. Ведь не маловажная причина перехода на Си - это читаемость программы её структурированность и возможность неоднократного применения модулей. По крайней мере я так рассматриваю. А опыта пока не хватает. Это я вижу хотябы по тому, что у меня от проекта к проекту пока меняются некоторые моменты оформления.

Думаю конечно скоро всё устаканится.

Спасибо за советы. beer.gif
Сергей Борщ
Цитата(alexander55 @ Dec 11 2007, 12:29) *
Приятно сразу понять логику работы всей системы глядя на один модуль.
У меня в main.c обычно находятся собственно main() и __low_level_init(). Глядя на main() можно попытаться понять логику работы всей системы, правда есть одно исключение smile.gif :
Код
int main()
{
    OS::Run();
}
Цитата(alexander55 @ Dec 11 2007, 12:29) *
Детали находятся за рамками и если требуются, то понятно, где это можно посмотреть (глядя на модуль).
Так RS232_transmit_handler() - это и есть детали. А логика описывается getchar(), putchar() и hasinput(). Мне так кажется...
Цитата(SasaVitebsk @ Dec 11 2007, 12:58) *
Прерывание - метки времени. То есть там целый набор. smile.gif в том числе вызовы опроса клавиатуры.
В таком случае наименьшие накладные, наверное, можно получить если "набор" описать в заголовочных файлах как static inline функции.
alexander55
Цитата(Сергей Борщ @ Dec 11 2007, 16:07) *
У меня в main.c обычно находятся собственно main() и __low_level_init(). Глядя на main() можно попытаться понять логику работы всей системы, правда есть одно исключение smile.gif :
Код
int main()
{
    OS::Run();
}

Это логичный и устоявшийся модуль. biggrin.gif

Цитата(Сергей Борщ @ Dec 11 2007, 16:07) *
Так RS232_transmit_handler() - это и есть детали. А логика описывается getchar(), putchar() и hasinput(). Мне так кажется...

В данном случае так.

Цитата(Сергей Борщ @ Dec 11 2007, 16:07) *
В таком случае наименьшие накладные, наверное, можно получить если "набор" описать в заголовочных файлах как static inline функции.

Или включить максимальную оптимизацию по требуемому параметру.
Сергей Борщ
Цитата(alexander55 @ Dec 11 2007, 15:39) *
Или включить максимальную оптимизацию по требуемому параметру.
Не поможет. Если части обработчика в виде функций раскиданы по разным модулям, а из обработчика видны как extern func(), то при любой оптимизации встраивания не будет, а компилятор будет сохранять/восстанавливать ненужные регистры. Если же все функции встраиваются - у компилятора широкое поле для оптимизаций
Panych
Подниму тему, чтоб не плодить...
Объявляю внешнюю функцию:
Код
extern void LCD_INIT ( void );

В main делаю вызов
Код
  LCD_INIT();

Не работает. Вставляю в main тело функции - все работает.
IAR не ругается и не предупреждает.
Файлы прикрепил. Помогите понять, плиз smile.gif
msalov
Цитата(Panych @ Apr 7 2008, 16:32) *
Подниму тему, чтоб не плодить...
Объявляю внешнюю функцию:
Код
extern void LCD_INIT ( void );

В main делаю вызов
Код
  LCD_INIT();

Не работает. Вставляю в main тело функции - все работает.
IAR не ругается и не предупреждает.
В пятницу текст выводился, все вроде было именно так организовано - а в понедельник...
Файлы прикрепил. Помогите понять, плиз smile.gif

Судя по вашему проекту, функция LCD_INIT() у Вас не внешняя а очень даже локальная. Если уж решили разбивать проект на модули - доводите дело до конца, а не сводите все "модули" в один через #include.
С минимальной правкой того что есть, можно перенести прототип функции LCD_INIT() до тела самой функции.
Код
void LCD_INIT ( void );
void LCD_INIT ( void )
{
...
}

И ещё совет: все прототипы лучше описывать в заголовочных файлах.
Panych
gotty
спасибо, сделал функцию внешней, IAR ругнулся на то, что в функции было объявлено
Код
extern void DELAYMKS ();
extern void DELAYMS ();

а не
Код
extern void DELAYMKS ( unsigned long mks );
extern void DELAYMS ( unsigned long ms );

исправил и все вроде заработало...
alux
Цитата(gotty @ Apr 7 2008, 16:45) *
И ещё совет: все прототипы лучше описывать в заголовочных файлах.
... и чтобы исключить многократные включения заголовочных файлов в начале каждого хидера применять "заглушку" типа:
Код
#ifndef XXX_H
#define XXX_H

..................

#endif
MrYuran
Цитата(Panych @ Apr 7 2008, 17:15) *
gotty
спасибо, сделал функцию внешней, IAR ругнулся на то, что в функции было объявлено
Код
extern void DELAYMKS ();
extern void DELAYMS ();

очень правильно ругнулся, ибо () означает void, а вы туда переменные суёте...
ivainc1789
Тема о многостраничных файлах, задам и свой махонький вопрос. А как описать внешние метки? Например, в main.c у меня описана метка Sleep. В другом файле модуля есть необходимость в безусловном переходе на Sleep типа goto Sleep. Сходу в документации не нашел как " ласково попросить" компилятор считать метку Sleep внешней? Конструкции типа extern label Sleep естественно не прокатывают...
Непомнящий Евгений
Цитата(ivainc1789 @ Apr 9 2008, 19:09) *
Тема о многостраничных файлах, задам и свой махонький вопрос. А как описать внешние метки? ...

#include <setjmp.h>
Описание можно посмотреть например здесь http://www.freetype.org/david/reliable-c.html#annex-A

Но можно вопрос - а зачем вам это надо???
rezident
Цитата(ivainc1789 @ Apr 9 2008, 21:09) *
Тема о многостраничных файлах, задам и свой махонький вопрос. А как описать внешние метки? Например, в main.c у меня описана метка Sleep. В другом файле модуля есть необходимость в безусловном переходе на Sleep типа goto Sleep. Сходу в документации не нашел как " ласково попросить" компилятор считать метку Sleep внешней? Конструкции типа extern label Sleep естественно не прокатывают...
bb-offtopic.gif Жесть! 07.gif Использование goto в программах на Си вообще считается моветоном, а чтобы еще и скакать по goto из одного файла в другой мне бы вообще в голову не пришло cranky.gif
Насколько я понимаю метка всегда привязана к оператору или функции, поэтому ее область видимости ограничена этой самой функцией или модулем. В самом стандарте ANSI C про goto говорится что
Цитата
6.8.6.1 The goto statement
Constraints
1 The identifier in a goto statement shall name a label located somewhere in the enclosing
function. A goto statement shall not jump from outside the scope of an identifier having
a variably modified type to inside the scope of that identifier.

Т.е. стандартными средствами Си "прыгнуть" по goto, допустим, внутрь другой функции нельзя. Тем более нельзя для функции, описанной в другом модуле.
sKWO
Добрый день!
Многофайловый проект, чёрт по.....
Подскажите, как обойти ошибку, извиняюсь что может быть повторяюсь.
Просто переопределения нигде не вижу, понимаю что чтото с включением файлов, пытался экстернить
но не помогло. Прикреплю проджект на всякий случай. Спросил на телесисах, но там молчок 05.gif
Error[e27]: Entry "EN" in module driver_lcd ( C:\Program Files\IAR Systems\test\testlsd\Debug\Obj\driver_lcd.r90 )
redefined in module main ( C:\Program Files\IAR Systems\test\testlsd\Debug\Obj\main.r90 )
ИАР версия 4.хх.
имею след код
файл bitdef.h
Код
#ifndef    BITDEF_H    
#define    BITDEF_H
#ifndef    BOOL_t  
#define    BOOL_t
typedef uint8_t bool_t;
#endif // BOOL_t
#ifndef    BitStr_t
#define    BitStr_t
typedef volatile uint8_t * port_t;
typedef struct{
  port_t Port;
  bool_t Bit;
}Port_Bit;
#endif // BitStr_t
#pragma inline=forced
void SETDDR(const Port_Bit * pb){
  *(pb->Port-1) |= 1 << pb->Bit;
}
#endif //BITDEF_H

файл driver_lcd.c
Код
#ifndef    driver_lcd_C  
#define    driver_lcd_C
#include "driver_lcd.h"
#include "delay.h"
// Команда на настройку выводов управления линиями управления ЖКИ на выход
void lcd_CMND_port_out(void)    {
                                        SETDDR(RW);\
                                        SETDDR(EN);\
                                        SETDDR(RS);\
                };
#endif //driver_lcd_C

файл driver_lcd.h
Код
#ifndef    driver_lcd_H
#define    driver_lcd_H
#include "bitdef.h"
// "E" Clock
Port_Bit dEN={&PORTB, 3};
const Port_Bit * EN = &dEN;
void lcd_CMND_port_out(void);
#endif //driver_lcd_H
alux
Цитата(sKWO @ Apr 13 2008, 13:55) *
файл driver_lcd.h
Код
#ifndef    driver_lcd_H
#define    driver_lcd_H
#include "bitdef.h"
// "E" Clock
Port_Bit dEN={&PORTB, 3};   <<<<<<<<!!!!!!!!!!
const Port_Bit * EN = &dEN;   <<<<<<<<!!!!!!!!!!
#endif //driver_lcd_H

У вас в driver_lcd.h вынесено определение указателя EN. И это, естественно, является причиной переопределения. Нарушено правило одного определения. Перенесите определения в driver_lcd.с, а в driver_lcd.h объявите эти переменные с модификатором extern.

PS. Это же касается и других переменных.
И уберите обратные слеши в определении lcd_CMND_port_out. Они там ни к чему.

PS2. И
Код
#ifndef    driver_lcd_C  
#define    driver_lcd_C
...
... это лишнее.
sKWO
Цитата(alux @ Apr 13 2008, 17:08) *
У вас в driver_lcd.h вынесено определение указателя EN. И это, естественно, является причиной переопределения. Нарушено правило одного определения. Перенесите определения в driver_lcd.с, а в driver_lcd.h объявите эти переменные с модификатором extern.

Спасибо добрый чел, а то как-то грустно было вместо файла driver_lcd.h в мейне подключать файл driver_lcd.c. Так компилится без ошибок. Поэтому и было
Код
#ifndef    driver_lcd_C  
#define    driver_lcd_C

smile.gif smile.gif smile.gif smile.gif
Сам знаю что не правильно, а с меня пиво beer.gif , щас программы нету под рукой но думаю что вопрос будет закрыт
Непомнящий Евгений
Цитата(sKWO @ Apr 13 2008, 14:55) *
Код
typedef uint8_t bool_t;
...
typedef struct{
  port_t Port;
  bool_t Bit;
}Port_Bit;
...
Port_Bit dEN={&PORTB, 3};


Странно как то - eсли вы объявили тип bool_t, то зачем туда запихивать 3????

И вообще, ИМХО, очень уж у вас сложный способ устанавливать биты в порту smile.gif
Может быть, конечно, ваш компилятор все это соптимизирует...

И еще вопросик - зачем вам такие конструкции:
Цитата
Код
#ifndef    BOOL_t  
#define    BOOL_t
typedef uint8_t bool_t;
#endif // BOOL_t

У вас bool_t что, в разных h-файлах объявляется? Объявляйте только в одном, и затем включайте везде, куда надо. Код сильно упростится...
alux
Цитата(Непомнящий Евгений @ Apr 14 2008, 07:44) *
И вообще, ИМХО, очень уж у вас сложный способ устанавливать биты в порту smile.gif

Присоединяюсь.
Код
#ifndef    BOOL_t  
#define    BOOL_t
typedef uint8_t bool_t;
#endif // BOOL_t

Подключите stdbool.h, и пользуйтесь bool как полноценным типом.
andrej2005
Цитата(jorikdima @ May 6 2007, 18:20) *
хидеры то подключены, а в опциях проекта пути к ним прописаны? (если они по поддиректориям распределены)


У меня была подобная проблема, только в MPLab для PIC. Думал рехнусь, и путь к линкеру был прописан в опциях проекта. Все решилось, когда взял в фигурные скобки етот путь.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.