Да, кто как привык, тот так и ляпает. Кому-то удобнее навалить всю кучу в один main.h, и затем его подключать везде. Кому-то удобнее иметь пару file.c и file.h и подключать file.h туда, где необходимо, создавая древовидную иерархию.
Тут еще гадит всю картину несовершенство языка Си в плане разделения, изоляции видимости. Любая ф-ция, которая не объявлена как static, будет видна во ВСЕМ проекте, независимо от инклюдов. Максимум - компилятор ругнется на то, что она явно не объявлена. Но имя ф-ции - видно везде и оно должно быть уникальным.
С #define дело попроще - они видны только там, где написаны или явно подключены .
Поэтому, вобщем-то языку Си всё равно, в какую кучу вы будете валить инклюды заголовков и всё равно, че вы будете писать в них.
А чтобы не было казусов с повторным подключением одного и того же заголовка к одному и тому же файлу (который приводит к ошибке переопределения), все заголовочные файлы должны иметь защиту в виде
#ifndef MY_FILE_H
#define MY_FILE_H
.... текст ....
#endif
, где MY_FILE_H - уникальное имя с указанием пути до файла, типа DRIVERS_EPD154_SPIDRV_H. Проги-редакторы (IDE разработчика) обычно умеют самостоятельно генерировать шаблон имени в дефайне.
Лично на мой вкус - мне больше нравится древовидная структура подключений. Хотя раньше пытался и так, и сяк писать.
Древовидная структура подразумевает, что из файла main.c вы все равно не будете напрямую вызывать функции, лежащие на самом нижнем уровне. Из main.c вы будете оперировать какими-то общими ф-циями, которые, в свою очередь, обращаются к низкоуровневым ф-циям.
На примере мигания светодиодом/
Файл main.c:
Код:
|
#include "signals.h" // подключен файл сигналов
/* программная задержка */
void Delay(volatile int count)
{ for ( ; count › 0; Count--); }
/*-----------------------------------*/
void main(void)
{
int mode = 1;
if (mode == 1)
Signal_1(); // вызов ф-ции из файла signals.c
else
Signal_2(); // вызов ф-ции из файла signals.c
}//------------------------------------- |
Файл signals.h:
Код:
|
#ifndef SIGNALS_H
#define SIGNALS_H
/* объявления ф-ций для использования их в других ф-лах */
void Singal_1(void);
void Singal_2(void);
#endif |
Файл signals.c:
Код:
|
#include "signals.h" // подключен собственный заголовок
#include "led_control.h" // подключен файл управления светодиодом
extern void Delay(volatile int count); //объявлена внешняя ф-ция для
/*-----------------------------------*/
void Singal_1(void)
{
Blink(5, 100); // 5 миганий с периодом 100 мс
Delay(10000);
Blink(10, 50); // 10 миганий с периодом 50 мс
}//------------------------------------
void Singal_2(void)
{
Blink(3, 10); // 3 мигания с периодом 10 мс
}//------------------------------------- |
Файл led_control.h:
Код:
|
#ifndef LED_CONTROL_H
#define LED_CONTROL_H
void Blink(int num, int period);
#endif |
Файл led_control.с:
Код:
|
#include "stm32f1xx.h" // подключен заголовочник самого МК
#include "led_control.h"
/* макросы */
#define LEDON GPIOA-›BSRR = 1‹‹5
#define LEDOFF GPIOA-›BRR = 1‹‹5
extern void Delay(volatile int count);
/*-----------------------------------*/
void Blink(int num, int period)
{
for (i = 0; i ‹ num; i++)
{
LEDON;
Delay(period / 2);
LEDOFF;
Delay(period / 2);
}
}//------------------------------------- |
ну и так далее.
Видим, что в main.c мы вызываем только ф-ции из signals.c, значит, в main.c нам не надо подключать ничего, кроме #include "signals.h".
А вот extern void Delay(volatile int count) написана как extern и означает, что ее определение есть в каком-то другом файле.
Заголовочник с описанием регистров и битов МК подключается только в led_control.c и не передается выше, поскольку выше по уровням нигде не используются операции непосредственно с регистрами МК.
Хотя, в принципе, в процессе написания, чтобы не лезть глубоко в дерево, можно низкоуровневые подключения вывести в main.c, прописав #include "led_control.h", а так же #include "stm32f1xx.h" и получить доступ к низкоуровневым ф-циям.
(правда, макросы LEDON и LEDOFF нам не будут доступны, поскольку они написаны в led_control.c и не подключаются, но мы можем их перенести в led_control.h).
Это бывает удобно, когда еще сам не знаешь, как бы получше написать тот или иной кусок.
Но оставлять весь неиспользуемый мусор - не очень хорошо.
Что касается подключения драйверов, то лично я подключаю так же только те, которые конкретно используются в данном файле. Хотя конкретно Си не разграничивает видимость, но я просто не люблю навала всего и вся. То есть, в файл вывода на дисплей как-то нет смысла подключать драйвер клавиатуры. Ну так ведь, правда?
Да нет, кончено вы можете свалить все драйверы в кучу, "на всякий случай", это не запрещено законами языка Си. Но чисто для себя, когда разбираешься и думаешь: "ну нафига тут болтается драйвер клавиатуры, драйвер АЦП, да еще и драйвер ЕЕПРОМ, когда реально в этом файле используется всего лишь драйвер светодиода???"
С другой стороны, древовидные подключения более сложны для восприятия. Проще конечно свалить в одну кучу и знать, что в этой куче "как то всё само собой выберется что надо".
PS.
Еще один вариант для разнообразия безобразия. Можно переписать файл signals.c вот так:
Код:
|
#include "signals.h"
#include "stm32f1xx.h" // подключен заголовочник МК
/* макросы */
#define LEDON GPIOA-›BSRR = 1‹‹5
#define LEDOFF GPIOA-›BRR = 1‹‹5
extern void Delay(volatile int count);
static void Blink(int num, int period); // объявлена статическая ф-ция из этого же файла
/*-----------------------------------*/
void Singal_1(void)
{
Blink(5, 100); // 5 миганий с периодом 100 мс
Delay(10000);
Blink(10, 50); // 10 миганий с периодом 50 мс
}//------------------------------------
void Singal_2(void)
{
Blink(3, 10); // 3 мигания с периодом 10 мс
}//-------------------------------------
/*===================================*/
/*-- статическая ф-ция, используемая только в этом файще */
static void Blink(int num, int period)
{
for (i = 0; i ‹ num; i++)
{
LEDON;
Delay(period / 2);
LEDOFF;
Delay(period / 2);
}
}//-------------------------------------- |
...и после этого файлы led_control.c (.h) больше не нужны. Статическая функция Blink находится в signals.c и изолирована от других файлов, ее никак нельзя вызвать, кроме как непосредственно из файла signals.c, это тоже принцип языка Си. Так же остаются изолированы макросы LEDON, LEDOFF, и так же #include "stm32f1xx.h" не распространяется за пределы файла signals.c
Вот эти моменты надо помнить, разбираясь с подключениями.
Сообщение от parovoZZ
|
не подключают, а на них ссылается main.c
|
Вот этот момент я дико не понял. Как это - "не подключает, но ссылается"??? В языке Си нет такого понятия как "ссылка на файл". Файл может либо быть подключен (#include), либо не быть подключен.
И если будут вызваны какие-то функции, описанные в другом файле, но они не будут объявлены (прототип ф-ции) в текущем файле (или подключены через #include), то это противоречит концепции языка и будет выдано предупреждение. Ф-ция-то сама по себе и может вызываться, но компилятор не может проверить соответствие списка аргументов ф-ции и вызов может пройти с ошибками. Базовые принципы языка Си нарушать не следует.
Лучше почитать книжки по Си - авторы Керниган и Ритчи.