parovoZZ

inline или макрос в многофайловых проектах?

24 сообщения в этой теме

parovoZZ    0

Есть у нас функция инициализации какого-то модуля или устройства, либо функция, которая просто возвращает значение регистра. Логичнее эти функции писать в файлах "драйверах", а не в main. Ну хотя бы с точки зрения переносимости кода между проектами. Но сразу появляется вопрос - что удобно программисту, то неудобно компилятору. Смысла в вызове однократно используемой функции или функции с одной командой нет - такие функции разумнее встроить в код. Но вот как быть - inline из другого *.c файла не вызывается, то бишь такую функцию надо писать в заголовке. Но такую функцию можно написать и как макрос (в том же заголовке - макрос в *.c файле тоже не виден али нет?). Так вот меня разрывает внутренняя устойчивая связь - как же поступить?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500
В 1/8/2019 в 01:29, parovoZZ сказал:

Есть у нас функция ..., которая просто возвращает значение регистра.

Не надо делать таких функций. Все, что делается оператором присваивания, должно делаться оператором присваивания, функция тут - лишняя сущность.

В остальном согласен с предыдущим оратором - оптимизаторы нынче умные пошли... многие "неудобности" проглатывают и не морщатся.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Функция даст больше контроля над данными, чем макрос. Макрос лучше использовать там где с функцией не все получается так как надо.
Я бы помог компилятору и всё же добавил бы inline. В возвращении функцией одного значения не вижу ничего плохого.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500
Только что, technik-1017 сказал:

В возвращении функцией одного значения не вижу ничего плохого

Главное, чтобы это было продиктовано какой-то необходимостью, а не сделано просто "ради красоты". Например, для атомарного доступа к многобайтным переменным или структурам, или для чего-то подобного. Если такой необходимости нет, то нет и принципиальной разницы между глобально видимой переменной и функцией для доступа к ней. И меньше раздумий о том, как лучше сделать :) 

Изменено пользователем ARV

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alexeyslav    642

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0

Ну вот пример. Есть такой цикл

          do
            {
                ADC_Change_in(ADC_channels[ADC_current_channel]);

                Sleep(SLEEP_MODE_ADC);

                Value_current[lsb][ADC_current_channel] = ADCL;
                Value_current[msb][ADC_current_channel] = ADCH;

            } while (ADC_current_channel--);

Sleep () определена в этом же файле.

В отдельном файле ADC.c определяю функцию:

void ADC_Change_in (uint8_t Analog_input)
{
    ADMUX = (1<<REFS1) | (0<<REFS0) | Analog_input;
}

И получаю размер кода 1232 байта. Комменчу эту функцию и вместо неё пишу в ADC.h:

#define ADC_Change_in(Analog_input)    ADMUX = (1<<REFS1) | (0<<REFS0) | (Analog_input)

Размер кода 1164 байта! Компилятор AVR/GNU C Compiler : 5.4.0. Оптимизация O3.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
7 часов назад, parovoZZ сказал:

Ну вот пример

выложите минимальный проект для компиляции.
у вас функция ADC_Change_in() не inline?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500

Вставлю своё скромное мнение.

1. 

10 часов назад, parovoZZ сказал:

Sleep () определена в этом же файле

То есть "сон" у вас логически увязан с текущим модулем каких-то измерений, а переключение каналов измерительных логически не увязано и находится в другом файле. Я верно понял?

2. 

void ADC_Change_in (uint8_t Analog_input)
{
    ADMUX = (1<<REFS1) | (0<<REFS0) | Analog_input;
}

Правила хорошего тона потребуют от вас изменить функцию как-то так:

void ADC_Change_in (uint8_t Analog_input)
{
  if(Analog_input < VALID_AIN)
    ADMUX = (1<<REFS1) | (0<<REFS0) | (Analog_input);
}

А это уже в макрос хуже ложится. А там, глядишь, и еще что-то понадробится добавлять...

Как итог: не лучше ли пересмотреть логику создания модулей программы так, чтобы все нужные функции оказались в нужных файлах? Тогда не надо будет ничего мудрить, а инлайн возьмет на себя компилятор - он очень хорошо инлайнит однократно вызываемые static-функции.

В качестве полукостыльного решения вашей проблемы порекомендую попробовать опцию компилятора/линкера -flto, которая вполне может проинлайнить и функции из других модулей...

Изменено пользователем ARV

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0
4 часа назад, technik-1017 сказал:

у вас функция ADC_Change_in() не inline?

Она определена в отдельном с файле. Поэтому что static, что inline, extern - компилятор её не сможет заинлайнить. Чтобы это случилось, её надо определить в заголовке, ну или в вызывающем файле. Первое меня устраивает, но возникает вопрос данного топика, второе меня категорически не устраивает).

31 минуту назад, ARV сказал:

То есть "сон" у вас логически увязан с текущим модулем каких-то измерений, а переключение каналов измерительных логически не увязано и находится в другом файле. Я верно понял?

Не совсем понял вопроса. Но отвечу. Данный цикл вызывается по сработке от таймера, чтобы обеспечить равномерную по времени оцифровку. АЦП включен в режиме свободного запуска. Я меняю канал мультиплексора и увожу ЦПУ в ADC Noise Reduction. Как только ЦПУ остановится, автоматически запустится АЦП на измерение. По окончании я выхожу из сна через обработчик прерывания от АЦП и снимаю показания. И так три раза.

37 минут назад, ARV сказал:

Правила хорошего тона потребуют от вас изменить функцию как-то так:

Когда над проектом работает несколько человек - да, несомненно. У меня же каналы определяются через #define (тупо числа из даташита) и они же заносятся в массив для возможности перебора. Если есть более изящное решение по переключению каналов, то извольте выложить=)

11 час назад, Alexeyslav сказал:

Выигрываем, конечно. Но какой ценой?

А чем я плачу? Размер кода уменьшен, скорость выполнения выше.

41 минуту назад, ARV сказал:

В качестве полукостыльного решения вашей проблемы порекомендую попробовать опцию компилятора/линкера -flto, которая вполне может проинлайнить и функции из других модулей...

Спасибо, почитаю.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500
Только что, parovoZZ сказал:

Не совсем понял вопроса

Если вы переключение каналов поместите в тот же файл, где и sleep() и замеры, проблема исчезнет. Я об этом.

Разбиение проекта на модули может быть по 2 основным принципам: по функционалу и по логике. По функционалу все, что касается АЦП, должно быть в одном файле-модуле, и в этом случае не понятно, с чего ИЗМЕРЕНИЕ ПРИ ПОМОЩИ АЦП у вас в одном файле, а ПЕРЕКЛЮЧЕНИЕ КАНАЛОВ АЦП в другом.

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

Только что, parovoZZ сказал:

её надо определить в заголовке

В заголовке определять функцию - дурной тон, не надо так делать.

Только что, parovoZZ сказал:

второе меня категорически не устраивает

Вот я и вопрошаю: почему?

Изменено пользователем ARV

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0
30 минут назад, ARV сказал:

Вот я и вопрошаю: почему?

Удобнее переносить файлы между проектами. Можно и одной страницей писать ( как это делают поклонники ардуины) - это даже удобнее при изучении исходников.

31 минуту назад, ARV сказал:

в этом случае не понятно, с чего ИЗМЕРЕНИЕ ПРИ ПОМОЩИ АЦП у вас в одном файле, а ПЕРЕКЛЮЧЕНИЕ КАНАЛОВ АЦП в другом.

Потом через макросы унесу в ADC.h

1 час назад, ARV сказал:

А это уже в макрос хуже ложится.

через

do
{
}while(0);

должно залезть=)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500

Вы не совсем правильно понимаете мою мысль... ну или я не могу её донести до вас понятным образом. Попробую еще раз.

Есть подход разбиения проекта на модули по периферии: adc - все, что касается АЦП, timer - все, что касается таймера, usart - все что касается USART и т.д.

При этом почти всегда происходит то, что у вас: функция в одном модуле, а используется в другом, что порождает накладные расходы.

Есть подход разбиения проекта на модули по ЛОГИКЕ АЛГОРИТМА: measure - все, что касается измерения (АЦП, таймер, который запускает АЦП, промежуточные буферы и т.п.), calculate - все, что касается бизнес-логики проекта (обработка измеренных значений, вычисление управляющих воздействий и т.п.) и так далее. Примеры надуманные, но должно быть ясно, в чем суть.

При этом вопросов, аналогичных вами описанным, не возникает в принципе. Хотя нет гарантии, что не возникнут другие вопросы :)

Изменено пользователем ARV

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0

Ну почему же? Понял. Я так и делаю. И именно из-за желания заинлайнить короткие и однократно вызываемые функции и возникла эта тема.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500

Значит, показалось :)

Современный компилятор, в частности, GCC, без всяких макросов прекрасно инлайнит короткие функции внутри одного модуля, а с LTO-оптимизацией может попробовать и межмодульно... 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
2 часа назад, ARV сказал:

Есть подход разбиения проекта на модули по периферии: adc - все, что касается АЦП, timer - все, что касается таймера, usart - все что касается USART и т.д.

Вот как раз сейчас делая проект для себя небольшой, на стм32, столкнулся с проблемой как логичней сделать. Хотелось бы отсебятины добавить.

Например куб делает так, он создает функцию TIM_init, GPIO_init

То есть в TIM все что касается регистров TIM а в GPIO что касается гпио

Но это оказалось очень не удобным!

Например мне надо настроить ногу для таймера, почему она должна настраиваться в GPIO если логичней это отнести к настройке таймера? Возможно это дело привычки.

То есть я выношу в отдельный файл все что касается получения температуры, а это вариант с DMA+TIMER или просто в цикле опрос. И вот тут выходит не логично хранить настройку ноги в функции GPIO :)

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0
4 минуты назад, ARV сказал:

а с LTO-оптимизацией может попробовать и межмодульно... 

но ему ж тогда придется функцию с квалификатором inline компилировать в отдельный модуль?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500

Я говорил о ситуациях без квалификаторов inline - вроде как и без этого все может быть сделано... Тем более что inline само по себе не гарантирует встраивание...

Только что, DrobyshevAlex сказал:

То есть я выношу в отдельный файл все что касается получения температуры, а это вариант с DMA+TIMER или просто в цикле опрос. И вот тут выходит не логично хранить настройку ноги в функции GPIO

Бинго! О чем я и говорил.

Я для себя применяю следующий способ, снимающий практически любой головняк в подобных случаях.

GCC имеет встроенные секции кода init0...init9, в которых можно размещать naked-функции, которые будут "вызваны" автоматически при компиляции ДО начала main. В каждом модуле я делаю такую функцию в секции с номером 6-8 (9 - это фактически main, а первые секции могут быть с еще не инициализированным стеком или не обнуленными/не проинициализированными static-переменными), где инициализирую всю периферию и т.п., необходимую для ЭТОГО модуля. В main при этом никаких init_xxxx() вообще не требуется.

Мне такой подход представляется очень удобным... хотя есть и критики.

Изменено пользователем ARV

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0
3 часа назад, ARV сказал:

В заголовке определять функцию - дурной тон, не надо так делать.

В заголовках атмела определения inline функций заменены на макросы. Значит, тема раскрыта.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alexeyslav    642

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
parovoZZ    0
11 час назад, ARV сказал:

Это не отменяет дурного тона :)

Просматриваю сейчас библиотеку LUFA - вообще не стесняются определения функции в заголовке ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ARV    500

Просмотрите исходник avr-libc или ядра linux - там goto и longjmp сплошняком... и тем не менее общепринято считать это дурным стилем программирования. Никто не расстреливает за это, но и в восторг не приходит. Просто имейте ввиду.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ваша публикация должна быть проверена модератором

Гость
Вы не авторизованы. Если у вас есть аккаунт, пожалуйста, войдите.
Ответить в тему...

×   Вставлено в виде отформатированного текста.   Восстановить форматирование

  Разрешено не более 75 смайлов.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставить изображения напрямую. Загрузите или вставьте изображения по ссылке.

Загрузка...

  • Похожие публикации

    • Автор: parovoZZ
      Я сейчас про случай, когда тактовая частота SPI равна половине или даже равна тактовой процессора. Соответственно, чем занять МК, пока идет отправка данных? У нас есть всего 16 тактов (или меньше). Обычно идет проверка флага опустошения буфера. Но тогда в чем профит аппаратного SPI, если на том же USI данные отправляются за те же 16 тактов, но МК занят тактированием(!)?. Вот чем занять МК? Отправить в остановку? Так дольше будем подготавливаться к остановке, потом уход на вектор прерывания (4 такта как минимум) и возврат из него. Ежели заниматься чем-то параллельно, то так же теряем время на обработку прерывания. Получается - только тупить в троттлинге?
    • Автор: parovoZZ
      Поставил LUFA, следом абсолютно не нужный мне ASF. Но в упор не понимаю - как создать проект на базе этой библиотеки из студии? Приходится вручную копировать папку с заголовочниками LUFA, прописывать пути в makefile, лишние телодвижения по добавлению папки в свойства проекта. Если я это делаю всё вручную, то тогда для чего это расширение? Примеры я могу и так покрутить. ЗЫ - не слишком высокий скилл в юзании Atmel Studio/
  • Сообщения

    • @kotenok Андрей! Увидел на снимке знакомые места! Всё на карте искал эту базу. Похоже на Гаджиев. Три года вахтовым методом обслуживали две ЕС-1036 и стыковали их с ПК. И это было с 1989-1991. Шли утром на работу, на улице  - 25 градусов, и рукой можно было потрогать лодку! В это время, ( 07.04.1989г )  как раз затонул "Комсомолец".( 42 человека погибло ). Погиб, в основном, из-за бездарности командования, как и его собрат "Курск" ( 12.08.2000г ) Погиб  весь экипаж. Все 118 человек. Некоторые, молодые матросы, только закончившие школу,  не успели пожить! Рано оборвалась их жизнь! И не один адмирал не ответил за это преступление в мирное время! Американцы боялись не качества наших лодок, а их количества. Это и были секретные данные, с которыми нам приходилось работать!
      P.S. Грустно как-то на душе! За всё что творилось, творится и происходит. Близко я к сердцу всё принимаю! 
    • Я так говорю про все, что мне понравилось
    • Определить получится если срисовать схему вручную.
    • Банальщина. Подобное мы писали на заборах лет 35 назад, будучи школьниками. И тоже с глупыми ошибками.
    • Здесь речь не о стабилизации напряжения БП, а о компенсации падения на проводах под нагрузкой. На измерительных проводах ток на порядки меньше, соответственно и падение мизерное, этим можно пренебречь.
      Но, боюсь,, для @Yurec66 всё это будет слишком сложно реализовать. Без обид. Чтобы этот вопрос более не мусолить, предлагаю замерить напряжение имеющегося БП непосредственно на нём и на контактах шуруповёрта, чтобы выяснить, так ли уж много падает на этих конкретных проводах . Под максимальной нагрузкой разумеется.
       
    •   U5 это сам мозг, максимум что он может - индикацию выводить. Инфу о напряжении должен брать с входа U6. Какое напряжение на 4 ноге U5? По идеи, когда на 4 ноге напряжение уменьшится ниже 2.3В, должна загореться индикация низкого напряжения. (удобно мерить на конденсаторе С32, делителем R32-R33 задаётся напряжение срабатывания). Однако, данная функция могла быть отключена в прошивке.  Если на 4 ноге напряжение в заряженном 3В, в разряженном около 2.3В и индикация не работает, то эта функция отключена (или красные светодиоды не работают).
    • @Dinisko Конденсаторы не причём! Чем больше ёмкость, тем низкие частоты лучше. А попробуйте добавить простейший усилительный каскад на одном транзисторе. R1 подберёте, чтобы на коллекторе было половина напряжения питания этого каскада. Транзистор любой  n-p-n типа КТ-315, КТ-3102.