Jump to content
parovoZZ

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

Recommended Posts

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

Share this post


Link to post
Share on other sites
В 1/8/2019 в 01:29, parovoZZ сказал:

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

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

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

Share this post


Link to post
Share on other sites

Литиевые батарейки Fanso для систем телеметрии и дистанционного контроля

Системы телеметрии находят все более широкое применение во многих отраслях на промышленных и коммунальных объектах. Требования, предъявляемые к условиям эксплуатации приборов телеметрии и, как следствие, источников питания для них, могут быть довольно жесткими. Fanso предоставляет широкую линейку продукции, рассчитанной на различные условия эксплуатации, что позволяет подобрать батарейку для каждого конкретного применения, в том числе и для устройств телеметрии.

Подробнее

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

Share this post


Link to post
Share on other sites
Posted (edited)
Только что, technik-1017 сказал:

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

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

Edited by ARV

Share this post


Link to post
Share on other sites
                     

Приглашаем на вебинар Решения для построения ультразвуковых счетчиков жидкостей и газов на базе MSP430

Компэл совместно с Texas Instruments 23 октября 2019 приглашают на вебинар, посвященный системам-на-кристалле для построения ультразвуковых расходомеров жидкостей и газов на базе ядра MSP430. Вебинар проводит Йоханн Ципперер – эксперт по ультразвуковым технологиям, непосредственно участвовавший в создании данного решения. На вебинаре компания Texas Instruments представит однокристальное решение, позволяющее создавать точные недорогие счетчики жидкостей и газов.

Подробнее...

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

Share this post


Link to post
Share on other sites

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

          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 итерации), все функции заинлайнились. То бишь проверка условия цикла и вызов функций в данном случае получается накладнее, чем тупой линейный код. И, разумеется, мы выигрываем также и в скорости выполнения кода.

Share this post


Link to post
Share on other sites
7 часов назад, parovoZZ сказал:

Ну вот пример

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

Share this post


Link to post
Share on other sites

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

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, которая вполне может проинлайнить и функции из других модулей...

Edited by ARV

Share this post


Link to post
Share on other sites
4 часа назад, technik-1017 сказал:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Share this post


Link to post
Share on other sites
Только что, parovoZZ сказал:

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

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

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

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

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

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

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

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

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

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

Edited by ARV

Share this post


Link to post
Share on other sites
30 минут назад, ARV сказал:

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

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

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

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

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

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

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

через

do
{
}while(0);

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

Share this post


Link to post
Share on other sites

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

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

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

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

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

Edited by ARV

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites
2 часа назад, ARV сказал:

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

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

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

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

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

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

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

 

 

Share this post


Link to post
Share on other sites
4 минуты назад, ARV сказал:

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

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

Share this post


Link to post
Share on other sites

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

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

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

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

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

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

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

Edited by ARV

Share this post


Link to post
Share on other sites
3 часа назад, ARV сказал:

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
11 час назад, ARV сказал:

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Similar Content

    • By parovoZZ
      AREF  - внутренний ИОН 1.1в
      Вот такой код снятия результатов:
      temp = ADC; if (ADCH & (1<<ADCH1)) // Если значение отрицательное { temp |= 0xFC00; temp = (~temp) + 1; } Value_current_lsb = (uint8_t)(temp >> 2); Никак не пойму - на выбранные дифф. входы надо подать 85 мВ, чтобы АЦП выдал 0. Но это могу списать на внутренний ОУ в виде УГ. Если подаю  -1,1 в - то получаю 127.  Здесь все верно. Но при подаче положительного смещения те же 127 получаю уже при 0,72в. Что за ерунда? Неужели ОУ на столько УГ? Либо же где-то теряется разряд?
    • By parovoZZ
      Ну, собственно, сабж. Питание - от 2.4 и до 3.6. На сигнальном выводе необходима половина напряжения питания (по постоянке, разумеется).
    • By parovoZZ
      Не секрет, что адресация глобальных переменных прямая, а переменных в стеке - косвенная. Стек в AVR программный, то бишь откусывается от ОЗУ. Так вот вопрос - при передаче в функцию (и обратно) больших объемов данных (которые невозможно передать через РОН) все же что будет производительнее - через глобальные переменные или через параметры? Понятно, что в функцию вида
      uint8_t My_super_function (uint8_t data); переменные уйдут через РОН,  а вот в такую
      void My_super_function (uint8_t *data, uint8_t *ret); через стек? Так может ну его нафик, стек этот?
    • By parovoZZ
      Я сейчас про случай, когда тактовая частота SPI равна половине или даже равна тактовой процессора. Соответственно, чем занять МК, пока идет отправка данных? У нас есть всего 16 тактов (или меньше). Обычно идет проверка флага опустошения буфера. Но тогда в чем профит аппаратного SPI, если на том же USI данные отправляются за те же 16 тактов, но МК занят тактированием(!)?. Вот чем занять МК? Отправить в остановку? Так дольше будем подготавливаться к остановке, потом уход на вектор прерывания (4 такта как минимум) и возврат из него. Ежели заниматься чем-то параллельно, то так же теряем время на обработку прерывания. Получается - только тупить в троттлинге?
    • By parovoZZ
      Поставил LUFA, следом абсолютно не нужный мне ASF. Но в упор не понимаю - как создать проект на базе этой библиотеки из студии? Приходится вручную копировать папку с заголовочниками LUFA, прописывать пути в makefile, лишние телодвижения по добавлению папки в свойства проекта. Если я это делаю всё вручную, то тогда для чего это расширение? Примеры я могу и так покрутить. ЗЫ - не слишком высокий скилл в юзании Atmel Studio/
×
×
  • Create New...