Например TDA7294

Форум РадиоКот • Просмотр темы - Структура исходника на Си
Форум РадиоКот
Здесь можно немножко помяукать :)





Текущее время: Ср апр 24, 2024 09:37:39

Часовой пояс: UTC + 3 часа


ПРЯМО СЕЙЧАС:



Начать новую тему Ответить на тему  [ Сообщений: 26 ]    , 2
Автор Сообщение
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 10:08:15 
Сверлит текстолит когтями
Аватар пользователя

Карма: 25
Рейтинг сообщений: 168
Зарегистрирован: Ср янв 29, 2014 08:41:31
Сообщений: 1231
Откуда: Баку
Рейтинг сообщения: 0
Сначала надо понять, что есть определение и есть описание.

Согласен, с терминологией у меня не все в порядке.

1. Заголовочный файл должен содержать только описания данных и кода. В нем не должно быть никаких определений! Исключением являются inline функции.

2. Локальные и публичные переменные и функции модуля (файла) должные определяться в самом этот исходном файле модуля на основании описаний заголовочного файла, который подключается директивой include.

Если я пишу unsigned int xyz, то это описание, которое вполне можно вписать в заголовок, а xyz = 1234 - определение, которое должно быть в *.с файле, верно? А определять константы в заголовке тоже нельзя?

3. Вложенное использование директив include (включение одного заголовочного файла внутри другого) вполне допустимо и часто используется.

Я так и делал раньше. Но выше мне сказали, что это чревато проблемами. Перенес все инклюды внутрь *.с файла.

_________________
Каждый имеет право на свое личное ошибочное мнение.

У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.


Вернуться наверх
 
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 10:54:16 
Друг Кота
Аватар пользователя

Карма: 1
Рейтинг сообщений: 157
Зарегистрирован: Пн окт 11, 2010 19:00:08
Сообщений: 3328
Рейтинг сообщения: 0
Zhuk72 писал(а):
Если я пишу unsigned int xyz, то это описание
Нет, это определение. Описанием (ссылкой на определение переменной) было бы в случае
Код:
extern unsigned int xyz


Вернуться наверх
 
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 13:37:27 
Первый раз сказал Мяу!

Карма: 13
Рейтинг сообщений: 17
Зарегистрирован: Чт июн 15, 2017 10:40:31
Сообщений: 32
Откуда: Екатеринбург
Рейтинг сообщения: 0
unsigned int xyz

Как Вам уже сказали выше, это определение. Дело в том, что с стандартными типами Вы не можете задать описание переменной иначе, чем указанием, что она внешняя (extern). Вы можете описать свой тип, класс, функцию. Но указав при этом еще и переменную, сразу получите определение.

Вот пример описания, уже давал его

Код:
struct smp_str {
   int  a;
   char b;
};


Но вот так получится уже не только описание, но и определение

Код:
struct smp_str {
   int  a;
   char b;
} var1;

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

определять константы в заголовке тоже нельзя?


А вот тут нужно еще разделить время компиляции и время исполнения. Константы времени компиляции описывать в заголовочном файле можно. Эти константы не будут размещаться в памяти в результирующем двоичном файле. А вот если Вы определите константную переменную, то для нее будет выделено место в памяти и в заголовочный файл помещать такое нельзя. Описывать здесь, как влияют на компиляцию квалификаторы (const, volatile, static, и прочие) не буду, так как тема обширная, особенно с учетом применения их к описанию и определению ссылочных переменных. Почитайте учебник по C.

это чревато проблемами

Это может быть чревато проблемами. Точнее, это может привести к рекурсивной цепочке загрузок (а загружает в, который в свою очередь снова загружает а, и так по кругу). Для исключения этого применяют директивы условной компиляции и простую внимательность. И это действительно применяется часто. В простых проектах смысла в этом нет, а вот в больших проектах со сложной иерархией (особенно, С++) вполне штатно используется.

_________________
Мир вокруг нас разумен настолько, насколько разумны мы сами. Профессионал не обязательно говорит умные слова, но зная самые глубины, способен объяснить их любому "на пальцах".


Вернуться наверх
 
PCBWay - всего $5 за 10 печатных плат, первый заказ для новых клиентов БЕСПЛАТЕН

Сборка печатных плат от $30 + БЕСПЛАТНАЯ доставка по всему миру + трафарет

Онлайн просмотровщик Gerber-файлов от PCBWay + Услуги 3D печати
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 13:40:52 
Открыл глаза

Карма: 1
Рейтинг сообщений: 3
Зарегистрирован: Вс июн 18, 2017 08:12:41
Сообщений: 76
Рейтинг сообщения: 1
На самом деле тема довольно интересная, разнообразная и творческая. И тем не менее, хватает людей, даже уже не новичков, которые допускают ошибки или просто не самым лучшим способом используют возможности современной редакции языка. Бегло прочел посты - да, есть такой момент, в частности, в заталкивании в .h-файлы всего того, что там быть не должно. Конечно, чистый язык Си (без ++) не назовешь модульным, в нем модульность если и есть, то через "одно место". Но тем не менее, раз хоть какая-то модульность есть, то её можно и нужно использовать.

Ключевое слово к модульности в Си - static. Именно с его помощью блокируется глобальная видимость функций и переменных.
Но обо всём по порядку. (кто всё это уже давно знает - можете не читать, ибо че еще раз снова одно и то же).
Вначале конечно сложно понять принцип модульности, но если разобраться - всё получается настолько удобным, что потом не хочется отказываться от модульности.
Модуль представляет собой некий код, который выполняет работу с каким-то функциональным узлом. Доступ к этому коду предоставляется только через функции взаимодействия с модулем. Внутренние функции и переменные модуля используются только кодом самого модуля и не должны быть доступны извне.

Пусть для первого примера это будет драйвер аппаратного SPI из микроконтроллера. Что требуется от такого драйвера? Первоначальная инициализация аппаратного SPI и отправка байтов (приём байтов пока не рассматриваем). Опционально - деинициализация (выключение) аппаратного SPI. Итак, доступ к этому модулю драйвера должен осуществляться извне только через вызов функций SPI_Init(), SPI_TX_byte(byte), SPI_Deinit(). Причем, при инициализации можно как передать параметры инициализации, так и доверить установку их самим драйвером, конкретный выбор зависит от программы. В нашем примере пусть драйвер устанавливает параметры сам.
Итак, мы определились с тем, что нам нужно от модуля драйвера. Теперь будем писать сам модуль. Понятное дело, что исполняемый код функций помещается в .c-файле, назовем его SPI_Drv.c и поместим его в папку с исходными кодами. (забегая вперед, скажу, что можно вообще выделить отдельную папку Drivers и указать для нее в настройках компилятора, что она тоже содержит исходные коды. Так более правильно с точки зрения модульности)
Но .c-файлы нельзя подключать директивой #include, а для всего остального кода будет недоступен текст из созданного нами файла. Значит, создаем .h-файл с таким же именем SPI_Drv.h, кладем его туда же, где и файл кода драйвера и подключаем этот заголовочный файл (с указанием полного пути, если он лежит в другой папке) к файлу main.c, прописав вначале его строчку #include "SPI_Drv.h" (либо #include "../Drivers/SPI_Drv.h").

Теперь в .h-файле прописываем прототипы функций, дающие доступ к коду модуля:

Код:
void SPI_Init(void);
void  SPI_TX_byte(unsigned char byte);
void SPI_Deinit(void);


Прототипы функций являются связующим звеном между вызовом функции и ее телом.
Теперь идем в .c-файл и пишем "заглушки" функций, т.е. некие функции-пустышки, ничего полезного не делающие, но зато показывающие структуру связей (кстати, это очень полезно вообще при написании своего кода).

Код:
/*-- Инициализация аппаратного SPI --*/
void SPI_Init(void)
{

}

/*-- Отправка байта по SPI --*/
void  SPI_TX_byte(unsigned char byte)
{

}

/*-- Деинициализация (отключение) SPI --*/
void SPI_Deinit(void)
{

}


Вот, считаем, что заготовка нашего драйвера уже написана! можем проверить ее работу (в пошаговом режиме отладки, конечно же!), вызвав из main.c функции драйвера, вот так:
SPI_Init();
Если вы всё делали внимательно и без ошибок, то ваш модуль будет работать. Можем вписать реальный код работы с реальным SPI и получить действующий драйвер.

Теперь второй акт балета :) Самое главное в модульности. Внутренние функции модуля, используемые только самим модулем.
Например, в функции SPI_Init может быть вызов функции Port_Init(), находящейся в том же файле. Это внутренняя функция, используемая только этим модулем драйвера. При обычном подходе эту функцию теоретически можно вызвать из любого места кода, из любого файла. Максимум, получите предупреждение компилятора, что функция не была объявлена в том файле, откуда вызывается. Но вызвана она будет и будет выполнена, иногда с косяками относительно параметров. А представим себе ситуацию, что таких функций с таким же именем - две. Компилятор уже выдаст ошибку - функция уже определена (написана) в таком-то месте, повторное определение невозможно.
Что делать? Очень просто! Пишем, что эта функция - статичная внутри этого файла и не имеет выхода наружу. т.е,

Код:
static void Port_Init(void)
{

}

и всё! Теперь мы можем вызывать ее только из того файла, в котором она есть. Сам вызов - точно так же, как и для любой другой функции. Прототип этой статичной функции тоже должен быть обхявлен как static и находиться должен в том же файле, но никак не в заголовочном .h-файле! Оно и понятно - статичная функция должна быть доступна только внутри одного файла, не подключаясь к другим.
Напомню, что прототип нужен, если функция будет вызвана по тексту ранее, чем ее тело (хм. как бы это попроще написать так, чтобы не усложнять терминами, потому что из-за терминов путаница то и есть.)
Проверяем, как это работает: в main.c пишем вызов Port_Init(); и получаем ошибку, что "референс такой то функции не найден". Вуаля! Мы заблокировали глобальную видимость функций и теперь вольны использовать СТАТИЧНЫЕ функции с одинаковыми именами внутри разных модулей.
Да, именно на таком принципе написана операционная система FreeRTOS. Кстати, можно посмотреть, как оно там всё сделано. Хотя букафф там дофига, но зато как работает то.

Еще моменты по модульности.
Аналогичным образом поступаем и с переменными. Те переменные, которые используются только внутри модуля, прописываются либо внутри функций модуля (локальная переменная функции), либо прописывается как СТАТИЧНАЯ глобальная переменная модуля вне функций в .c-файле. Переменные, которые принимает и возвращает модуль, крайне желательно передавать только через параметры функций. Те же принципы относятся и к структурам, перечислениям, типам.
В основном, модуль должен работать как самостоятельная и самодостаточная единица, а другие участки кода не должны зависеть от него, не должны знать того, как он устроен, какие там есть переменные и функции. Всё общение с модулем - только через предоставленные им функции и параметры функций. Это позволяет легко заменять один модуль другим. Например, если бы в рассмотренном примере имена функций были бы Periph_Init() и Transmitt(data), то модуль драйвера SPI можно было бы заменить на модуль драйвера UART, и главный код не заметил бы подмены.

Очень важно разобраться с правильным оформлением и включением переменных (равно как и структур, типов, объединений) в состав модуля. Ошибки в этом могут приводить к труднообъяснимому краху компиляции кода. Например, вы пишите какую-либо переменную в .h-файл модуля. Теперь подумайте - эта переменная будет объявлена как ГЛОБАЛЬНАЯ переменная для ВСЕГО кода, начиная от точки подключения. А попытка тут же инициализовать эту переменную может приводить к краху выделения памяти под нее. Однако, переменную по видимости можно заблокировать static-ом. Тогда она будет видна непосредственно в этом модуле и в модуле выше уровнем, к которому подключен исходный модуль.
Нужно очень внимательно отслеживать взаимосвязи и пределы видимости. Если действительно нужна такая переменная, которая будет глобально использоваться как в модуле, так и в main.c или другом файле, пропишите эту переменную в main.c или main.h.

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

Рассмотрим на примере некоего гипотетического устройства на интерфейсе SPI. Для этого возьмем написанный нами драйвер SPI и подключим его в качестве дочернего модуля в другой модуль, который теперь будет называться "драйвером устройства". По аналогии, создаем модуль для устройства - файлы Device_Drv.c, Device_Drv.h, подключаем Device_Drv.h к файлу main.c (предыдущее подключение SPI_Drv.h оттуда убираем!!).
Нашему модулю драйвера устройства потребуется уже написанный нами модуль драйвера SPI, подключаем его к файлу Device_Drv.c. Заметьте структуру подключений. Драйвер SPI подключен только к драйверу устройства, а не к main.c, отчего доступ к SPI формально есть только у драйвера устройства. Формально! Поскольку драйвер SPI всё еще может быть подключен к любому другому модулю, так и напрямую к main.c. И это тоже логично, поскольку на физической шине SPI может сидеть несколько устройств.
Теперь аналогичным образом в файле Device_Drv.h запишем прототипы функций, через которые пойдет взаимодействие с нашим устройством в целом.
Код:
void Device_Init(void);    // инит (запуск) устройства
void Device_Execution(unsigned char command_code, unsigned int param);   // некоторое действие устройства

а в файле Device_Drv.c пишем эти функции, вот так:
Код:
/*-- инит (запуск) устройства --*/
void Device_Init(void)
{
    SPI_Init();   // инит SPI-интерфейса
    SPI_TX_byte(0x31);    // отправка команды включения устройства
    SPI_TX_byte(0x54);    // отправка команды настройки параметров устройства
    SPI_TX_byte(0xA5);    // отправка параметра устройства
    SPI_TX_byte(0x7C);    // отправка параметра устройства
}

/*-- команда устройству --*/
void Device_Execution(unsigned char command_code, unsigned int param)
{
    SPI_TX_byte(command_code);    // отправка команды
    SPI_TX_byte(param >> 8);         // отправка параметра команды (старший байт)
    SPI_TX_byte(param);                 // отправка параметра команды (младший байт)
}


Вот, таким образом мы создали полноценный модульный драйвер устройства, в котором прослеживается четкая иерархическая структура. Я не стал усложнять излишками в виде возврата кода ошибки и прочих наворотов типа проверки занятости, хотя в реальном, не учебном коде конечно это необходимо.
Теперь из главного файла мы можем работать с нашим устройством с помощью компактной короткой записи, и при этом, главный файл совершенно не перегружен фукнционалом и мы можем сосредоточиться лишь на последовательности команд устройству. Кстати, можно добавить еще один уровень вложенности (вернее, абстракции), переведя последовательности отправляемых команд в еще один модуль. И этот новый модуль теперь будет называться не уже драйвером, а будет относиться к классу Middleware - "средний уровень". Допустим, наше гипотетическое устройство - это управлялка манипулятором. Тогда в новый модуль мы включим функции, такие как например Set_Home_Position(); Move_to_position(x, y); Taking_cargo(); Причем, этот модуль может включать в себя модули, работающие с датчиками положения манипулятора и обеспечивать обратную связь по координатам.
Такие абстракции от нижних уровней (которые уже были написаны и отлажены ранее) позволяет программисту сосредоточиться на решении непосредственно задачи в целом, не отвлекаясь на низкоуровневые операции. Да, такую же структуру можно создать и в одном файле, но представьте себе навигацию по такому файлу!

Еще немаловажный момент - использование extern. С одной стороны это упрощает иерархию и убирает сложнозапутанные вкладывания и завязывания, с другой стороны делает модуль зависимым от внешнего кода. Но если пишешь некий комплект модулей, в которых есть заложенное ранее единообразие, то применение extern порядком упрощает жизнь. Например, есть функция или переменная, которая может использоваться любым модулем. Например, функция задержки Delay(time), написанная в каком-то файле delay.c. Да, можно было сделать файл delay.h и подключать его ко всем .c-файлам. Но вдруг у этого файла изменится имя или расположение? Придется переписывать везде. Ну а если в каждый модуль прописать функцию, объявив ее внешней - extern void Delay(volatile unsigned int) ? Теперь нам всё равно, где и в каком файле написано тело функции Delay, нам лишь важно, что она где-то есть и называется именно так и содержит именно такую структуру, как в прототипе.
Однако, если мы всё-таки забыли написать тело функции, компилятор выдаст ошибку. А представим себе, что таких функций может быть не одна, и нам заранее неизвестно, какие нужны?
На такой случай есть еще один инструмент - слабосвязанные функции, weak. Это уже из свежей редакции языка Си.
Суть слабосвязанных функций такова. Допустим, нам нужна некоторая внешняя (вне нашего модуля) функция, реализующая что-то. Пока эта функция вне модуля есть (существует написанная), всё в порядке, всё работает. Но как только та функция исчезла (стерли мы ее нафик или не подключили еще), работать уже не будет - функции то нету. Чтобы избежать этого, нам нужна местная (внутри модуля) функция с таким же именем (хотя ее функционал может быть совершенно другим). В обычных условиях нам придется раскомментировать строки или закомментировать их обратно. Это не производительно.
И вот именно тут на помощь приходит weak-функция. Она точно такая же, но при ее написании указывается атрибут weak. Теперь всё работает автоматически. Нет внешней функции - работает внутренняя (которая может быть простой заглушкой), есть внешняя функция - работает уже именно внешняя, а внутренняя уже не доступна. Это хороший способ для построения межмодульных связей на этапе написания, когда несуществующие еще функции заменяются заглушками, не приводя к ошибкам компиляции.

Ну чтож, кто дочитал эту писанину до конца - тот молодец! :) Те, кто уже давно всё это знает - тем более молодцы! :)
Короче говоря, выводы:
- в идеале, модули должны быть самодостаточными и не зависеть от внешнего кода. На практике не всегда это возможно, да и не всегда это нужно, но основной принцип именно такой.
- изоляция функций и глобальных переменных внутри модуля - с помощью квалификатора static
- заголовочный файл модуля .h может содержать только то, что модуль предоставляет наружу - прототипы наружных функций, переменные и структуры, предоставляемые модулем в качестве уникальных для модуля и допускаемых для использования во внешнем коде. Не прописывайте в заголовочном файле модуля глобальных переменных, не относящихся к модулю.
- модули могут подключаться в иерархической цепочке, вкладываясь друг в друга.
- применяйте extern для упрощения взаимосвязей в логически связанных модулях, но избегайте чрезмерного extern, чтобы не потерять концепции модульности
- отделяйте уровни абстракций модулей. Только лишь самый низкий уровень абстракции может непосредственно работать с периферией микроконтроллера. Остальные уровни должны быть аппаратно-независимыми. Верхний уровень должен выполнять общие задачи, типа "переместиться на 20 см влево". Промежуточные, "средние" модули могут работать с драйверами внешних (относительно микроконтроллера) устройств и являются так же в большей степени аппаратнонезависимыми. Это обеспечит переносимость кода и быструю его интеграцию в другие ваши проекты.

Сорри за крайне очень длинный текст :) надеюсь, кому-то это может быть полезным. надеюсь, написанное достаточно понимаемо, постарался не усложнять

_________________
Ёшкин кот обормот


Вернуться наверх
 
Выбираем схему BMS для заряда литий-железофосфатных (LiFePO4) аккумуляторов

Обязательным условием долгой и стабильной работы Li-FePO4-аккумуляторов, в том числе и производства EVE Energy, является применение специализированных BMS-микросхем. Литий-железофосфатные АКБ отличаются такими характеристиками, как высокая многократность циклов заряда-разряда, безопасность, возможность быстрой зарядки, устойчивость к буферному режиму работы и приемлемая стоимость. Но для этих АКБ очень важен контроль процесса заряда и разряда для избегания воздействия внешнего зарядного напряжения после достижения 100% заряда. Инженеры КОМПЭЛ подготовили список таких решений от разных производителей.

Подробнее>>
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 14:06:08 
Сверлит текстолит когтями
Аватар пользователя

Карма: 25
Рейтинг сообщений: 168
Зарегистрирован: Ср янв 29, 2014 08:41:31
Сообщений: 1231
Откуда: Баку
Рейтинг сообщения: 0
Ясно, всем спасибо за науку :)

_________________
Каждый имеет право на свое личное ошибочное мнение.

У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.


Вернуться наверх
 
Новый аккумулятор EVE серии PLM для GSM-трекеров, работающих в жёстких условиях (до -40°С)

Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре. Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств.

Подробнее>>
Не в сети
 Заголовок сообщения: Re: Структура исходника на Си
СообщениеДобавлено: Вс июн 18, 2017 20:44:16 
Открыл глаза

Карма: 1
Рейтинг сообщений: 3
Зарегистрирован: Вс июн 18, 2017 08:12:41
Сообщений: 76
Рейтинг сообщения: 0
Эх, раззудись плечо, разойдись рука :) Писать так песать :) А то топикстартер долго будет мучиться в поисках.

Итак, пусть у нас будет гипотетический индикатор (типа знакосинтезирующего двустрочника 1602), подключенный по гипотетическому интерфейсу и нам на него надо чето вывести. Вначале определяемся со структурой файлов.
Код:
-- Drivers (папка драйверов)
|   | -- Hypothetical_LCD_Drv (папка драйвера дисплея)
|         | -- Hypo_LCD_Interface.c    (драйвер интерфейса)
|         | -- Hypo_LCD_Interface.h    (заголовок его)
|         | -- Hypo_LCD_Drv.c           (драйвер дисплея)
|         | -- Hypo_LCD_Drv.h           (заголовок его)
|
-- Middleware (папка для среднеуровневого кода)
|   | -- Display.c     (модуль работы с дисплеем)
|   | -- Display.h     (заголовок этого модуля)
|
-- Source (исходники общего кода)
|   | -- main.c (главный файл)
|   | -- inits.c  (файл со всякими инициализациями)
|   | -- delays.c (файл с задержками)
|   | -- tiny_printf.c  (файл с облегченными функциями printf)
|
-- (папка с системными файлами типа CMSIS, startup)


Теперь главный файл main.c. Он ничего "не знает" о типе дисплея - графический ли он или знакосинтезирующий, количество точек или символов, и что это вообще дисплей, а не например UART-терминал или даже вообще пять светодиодов в кружочек (такое тоже возможно!). Главное, что он "знает", что есть некий модуль "Display", к которому и нужно обращаться. Это и есть самый высокий уровень абстракции.
(Функции инициализации дисплея и его интерфейса опущены для экономии букаффф. Предполагаем, что уже всё иничено и готово)
Код:
/*------ std header Includes ----*/
#include <stdint.h>
/*------ Projec Includes --------*/
/*------ Module Includes --------*/
#include "Display.h"             // подключен модуль работы с дисплеем

//.....................................
/* External function prototypes */
extern void Delay_ms(volatile unsigned int time);   // подключена внешняя функция задержки
/**
**=====================
**
**  Abstract: main program
**  TODO
**=====================
*/
int main(void)
{

  WelcomeScreen();     // отображаем какое-то приветствие
  Delay_ms(5000);      // и после задержки на 5 с
  int voltage;
  voltage = 230;
  MainScreen(voltage); // отображается главный экран с параметром

  //******************************
  while (1)
  {   } /* while (1) */
} /* main() */
/* ========== END main() ==============*/


Отлично. Теперь пишем модуль дисплея Display.c(.h). В этом модуле мы должны составить текстовое сообщение, указать его позицию на дисплее и соппснно отправить его в драйвер дисплея. Именно на этом этапе определяется, какого типа у нас дисплей и какого размера он. Однако, модуль по-прежнему не знает, как работать с дисплеем. Модуль только знает, что надо вывести некоторое количество символов в указанной позиции. Это - промежуточный, средний уровень, когда известно, что и куда надо сделать, но еще непонятно, как этого добиться.
В .h-файле есть прототипы функций void WelcomeScreen(void); и void MainScreen(int value)
А .с-файл содержит код:
Код:
/*------ std header Includes ----*/
#include <stdint.h>
/*------ Projec Includes --------*/
/*------ Module Includes --------*/
#include "Display.h"              // подключен заголовочный файл этого же модуля
#include "../Drivers/Hypothetical_LCD_Drv/Hypo_LCD_Drv.h"   // подключен драйвер дисплея

//----------
/* Private (static!) variables */
static char text_buf[40];   // текстовый буфер
static int length;         // длина сформированного текста
//.....................................
/* External function prototypes */
extern int siprintf(char *buf, const char *fmt, ...);   // подключена внешняя функция sprintf
/**
**=====================
**
**  Abstract: Global (out-module) functions
**  TODO
**=====================
*/
void WelcomeScreen(void)
{
   int error, x_coord, y_coord;
   y_coord = 0;     // строка 0
   x_coord = 0;     // позиция 0
   length = siprintf(text_buf, "Hello, world, its me");   // текст сообщения
   error = Print_on_Display(text_buf, y_coord, x_coord, length);    // отправляем в дисплей через функцию во внешнем модуле
   
   y_coord = 1;    // строка 1
   x_coord = 3;    // позиция 3
   length = siprintf(text_buf, "Please, wait...");
   error = Print_on_Display(text_buf, y_coord, x_coord, length);
}
void MainScreen(int value)
{
   int error, x_coord, y_coord;
   y_coord = 0;
   x_coord = 0;
   length = siprintf(text_buf, "Voltage: %i V", value);
   error = Print_on_Display(text_buf, y_coord, x_coord, length);
}

контроль возвращаемого значения ошибки желательно использовать для случая, когда текстовое сообщение не вошло по длине, и например реализовать перенос на другую строку оставшейся части сообщения.

Тааак, это уже ближе, теперь мы подобрались вплотную к выводу именно на наш подключенный дисплей и будем писать драйвер Hypo_LCD_Drv.с(.h). Мы уже знаем, что это за дисплей (классический знакосинтезирующий двустрочник 1602-типа). Это уже один из нижних уровней абстракции, на уровне взаимодействия со внешними устройствами. То есть, драйвер дисплея. Он определяет, в какой последовательности надо подавать команды, какие режимы дисплея надо включить и как загрузить символы чтобы их отобразить. Но драйвер дисплея как правило не знает, по какому физическому интерфейсу дисплей подключен к контроллеру, какие ноги нужно дергать и какую периферию микроконтроллера надо использовать.
В .h-файле есть прототип функции int Print_on_Display(char *buf, int line, int position, int length);
А .с-файл содержит код:
Код:
/*------ std header Includes ----*/
#include <stdint.h>
/*------ Projec Includes --------*/
/*------ Module Includes --------*/
#include "Hypo_LCD_Drv.h"           // подключен заголовочный файл этого же модуля
#include "Hypo_LCD_Interface.h"   // подключен заголовочный файл драйвера интерфейса дисплея
/**
**=====================
**
**  Abstract: in-module (static!) functions
**  TODO
**=====================
*/
static void Set_Cursor(int line, int position)
{

}

/* заглушки нереализованных во внешних модулях функций */
__attribute__((weak)) void Write_to_LCD(char *buf) 
{
   char tmp;
   tmp = *buf;
}
/**
**=====================
**
**  Abstract: Global (out-molule) functions
**  TODO
**=====================
*/
int Print_on_Display(char *buf, int line, int position, int length)
{
   Set_Cursor(line, position);
   while (length--)
   {
      Write_to_LCD(buf);
      *buf++;
   }
   return 0;
}

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

И наконец-то мы добрались непосредственно до работы с периферией микроконтроллера. Драйвер интерфейса дисплея Hypo_LCD_Interface.c(.h), самый нижний уровень абстракции. Этот драйвер определяет, по какому интерфейсу физически подключен наш дисплей и какие аппаратные модули микроконтроллера надо будет задействовать - I2C, SPI или параллельный вывод.
Заметьте, что все предыдущие до этого уровни были аппаратно-независимыми и они с легкостью могут переноситься между разными микроконтроллерами без какой-либо переделки.

Вот, теперь наша модульная система имеет законченный вид. Конечно, приведенная структура не является единственно возможной, некоторые компоненты могут отсутствовать или входить в состав других модулей, а может быть и более сложная структура. Например, если у нас графический дисплей, то как сформировать шрифт, куда его впихнуть? После того, как была разобрана текстовая строка с помощью одной из встроенных функций print, управление должно передаваться модулю растровой графики, в состав которого входит функционал извлечения и формирования растрового шрифта. Поиск символа в шрифте и его извлечение - это отдельная и тоже интересная тема. Приходится находить компромисс между универсальностью, размером кода шрифта и скоростью его работы. Наиболее универсальный метод - он же и наиболее громоздкий и медленный.
Иногда бывает, что писать вот так, сверху вниз, бывает слишком сложно, потому что неизвестно, как работают нижние уровни. И тогда приходится сначала например написать и отладить драйвер, а потом уже подставлять его и корректировать код.

Да, и еще одна отдельная тема - операционные системы (ОРСВ) и интеграция кода в операционку. Там из-за несколько других понятий и распараллеливания процессов приходится менять подход. Уже нельзя напрямую так взять и в любой момент отправить что-то в дисплей. Поскольку там идет распараллеливание процессов, то может случиться так, что два процесса одновременно начинают отправлять что-то своё в дисплей и получается каша. Приходится принимать соглашение, что доступ к общему ресурсу защищается мьютексом.

_________________
Ёшкин кот обормот


Вернуться наверх
 
Показать сообщения за:  Сортировать по:  Вернуться наверх
Начать новую тему Ответить на тему  [ Сообщений: 26 ]    , 2

Часовой пояс: UTC + 3 часа


Кто сейчас на форуме

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 17


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Русская поддержка phpBB
Extended by Karma MOD © 2007—2012 m157y
Extended by Topic Tags MOD © 2012 m157y