Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Использование макросов с аргументами в Си
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
MaxiMuz
Для удобства работы с портами ввода/вывода (на STM32 в Keil) написал небольшой хидер, приведу его часть :
Код
#define SbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BS##Nbit
#define RbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BR##Nbit
#define SmbitP(Port,mask)    GPIO##Port->BSRR=(mask)
#define RmbitP(Port,mask)    GPIO##Port->BSRR=(mask)<<16

в программе использую следующие варианты включения макросов:
Код
#define  LCD_dataShift 1


void LCD_wrAdr (u8 Adr)
{
    SmbitP(A,((Adr&0x0f)<<LCD_dataShift)); // выставляем на шину адрес
    RbitP(A,7); // вкл. на запись адреса A0=0
    SbitP(A,5); // вкл.строба записи
    delay(2000);// Задержка ~ 100нс
    RbitP(A,5); // снимаем строб записи
    RmbitP(A,(0x0f<<LCD_dataShift)); // Сброс битов шины DBх
    delay(5000);// Задержка ~ 200нс
}

У меня вопрос: на сколько корректно такое применение макросов ? т.к. в случае с RbitP(A,7) в макрос подставляется символ , который при обединение с остальным текстом сам является библиотечным макросом. Т.е. до каких пор происходит раскрытие макроса ?
Tarbal
Бегло посмотрел. Вроде все нормально.
Макросы тупо подставляют параметры как текст не вычисляя их значение.
Если вы вместо параметра впишите текст и не будет синтаксических ошибок, то все пучком.
SSerge
Цитата(MaxiMuz @ Nov 28 2013, 14:32) *
Т.е. до каких пор происходит раскрытие макроса ?

До упора sm.gif
После обработки макро разбор продолжается от начала текста, получившегося в результате макроподстановки.
MrYuran
Не совсем понятен смысл таких макросов.
Если абстрагироваться, то нужно и от пинов, и от портов, и от уровней.
А это - всем известные макросы Аскольда Волкова или их подобие в виде шаблонов с++ (были пара тем)
demiurg_spb
Цитата(MrYuran @ Nov 29 2013, 15:48) *
+1
MaxiMuz
Цитата(MrYuran @ Nov 29 2013, 14:48) *
Не совсем понятен смысл таких макросов.
Если абстрагироваться, то нужно и от пинов, и от портов, и от уровней.

а смысл - вместо записи GPIOA->BSRR=GPIO_BSRR_BS2 писать SbitP(A,2) помоему так нагляднее , и букф меньше.
Вот по поводу абстрагирования от пинов, добавим еще пару макросов для именования выводов #define LCD_A0 7 и #define LCD_WR1 5 :
Код
#define  LCD_A0    7 // Выбор: Адрес A0=L/ Данные A0=H
#define  LCD_WR1     5 // Запись в модуль: H-активный уровень

#define  LCD_dataShift 1

void LCD_wrAdr (u8 Adr)
{
    SmbitP(A,((Adr&0x0f)<<LCD_dataShift)); // выставляем на шину адрес
    RbitP(A,LCD_A0); // вкл. на запись адреса A0=0
    SbitP(A,LCD_WR1); // вкл.строба записи
    delay(2000);// Задержка ~ 100нс
    RbitP(A,LCD_WR1); // снимаем строб записи
    RmbitP(A,(0x0f<<LCD_dataShift)); // Сброс битов шины DBх
    delay(5000);// Задержка ~ 200нс
}

В этом случае получается что перед тем как раскрыть макрос, нужно вместо букв параметра подставить то что означают эти буквы. Как в таком случае поведет себя предпроцессор , корректна ли такая запись с точки зрения стандарта написания макросов ?
demiurg_spb
Цитата(MaxiMuz @ Nov 29 2013, 16:29) *

Вы не до конца поняли намёк про макросы Аскольда Волкова.
Не поленитесь - погуглите да изучите.
Потом будете себя укорять за то, что слишком поздно услышали об этих "сакральных" знаниях.
По теме: то, что вы изобразили в последнем примере работать не будет. Требуется допиливание макросов. Вот так:
Код
#define _RbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BR##Nbit
#define RbitP(Port,Nbit)     _RbitP(Port,Nbit)
_Pasha
Я обычно так делаю
Код
#define LCD_WR1_pin 4
static inline void LCD_wr1(const char value)
{
  GPIOB->BSRR = 1 << (LCD_WR1_pin + value?0:16);
}

Никаких макро и никаких неясных абстракций. Все конкретно.
winipuh
Цитата(_Pasha @ Nov 29 2013, 22:19) *
Код
#define LCD_WR1_pin 4
static inline void LCD_wr1(const char value)
{
  GPIOB->BSRR = 1 << (LCD_WR1_pin + value?0:16);
}

Всего два вопроса:
  1. А зачем у аргумента функции квалификатор const? Тут же параметр не по ссылке передается, а по значению. blink.gif
  2. 1 << (LCD_WR1_pin + value ? 0 : 16); <— Вы тут часом скобочки не забыли? sm.gif
    Я о том, что приоритет операции "+" выше, чем у тернарной операции "?:"
    Надо бы так: 1 << (LCD_WR1_pin + (value ? 0 : 16));
demiurg_spb
Цитата(winipuh @ Dec 1 2013, 19:37) *
Ну и если ещё продолжить придираться, то аргумент функции размером меньше int на ARM архитектуре менее оптимален. Так что char'у тут не место....
_Pasha
Скобочки - запросто мог потерять.
Цитата(demiurg_spb @ Dec 2 2013, 08:39) *
Ну и если ещё продолжить придираться, то аргумент функции размером меньше int на ARM архитектуре менее оптимален. Так что char'у тут не место....


Если нужны вычисления на этапе выполнения, то для чего вообще эти инлайны? Это несерьезно. В конце концов, bit band существует.
winipuh
Я не придраться sad.gif
Просто уже и раньше кое-где встречал наподобие
Код
void func(const int x)
{
     ....
}

Например, в исходниках scmRTOS. Теперь вот и у Паши (уважаемого, надо сказать, человека sm.gif).
Вот я и спрашиваю... Вдруг в этом есть какой-то глубокий смысл, а я и не в курсе... laughing.gif
ViKo
Цитата(winipuh @ Dec 2 2013, 11:24) *
Вот я и спрашиваю... Вдруг в этом есть какой-то глубокий смысл, а я и не в курсе... laughing.gif

Ну, вдруг кто-то (или сам автор) задаст в value переменную. А компилятор - бац - нельзя! rolleyes.gif
winipuh
Цитата(ViKo @ Dec 2 2013, 12:36) *
Ну, вдруг кто-то (или сам автор) задаст в value переменную. А компилятор - бац - нельзя! rolleyes.gif

Шутить изволите? sm.gif
ViKo
Цитата(winipuh @ Dec 2 2013, 12:50) *
Шутить изволите? sm.gif

По-моему, в книге Шилдта описан этот прием. Для библиотечных функций.
demiurg_spb
Цитата(_Pasha @ Dec 2 2013, 10:29) *
Это несерьезно.
Для меня это никак не определят серьёзность.
Просто компиляторы и уровни оптимизации бывают разные - зачем делать не единообразно?
Где есть инлайн - так, а где его нет - сяк... На мой вкус это несъедобно.
А что касается макросов, то уж что-что, то для GPIO - самое оно. Обеспечивается 100% унификация и переносимость между всеми платформами...
MaxiMuz
Цитата(_Pasha @ Nov 29 2013, 21:19) *
Я обычно так делаю
Код
#define LCD_WR1_pin 4
static inline void LCD_wr1(const char value)
{
  GPIOB->BSRR = 1 << (LCD_WR1_pin + value?0:16);
}

Никаких макро и никаких неясных абстракций. Все конкретно.

по порядку:
1) Во первых Keil 4.7 не дает инлайнить функции.
2) Не ясен смысла записи GPIOB->BSRR = 1 << (LCD_WR1_pin + value?0:16); нужно передать в порт фиксированное значение ( номер пина)
3) Нет возможности изменить порт вывода.


И еще , что делает выражение value?0:16 и зачем ? я вообще не понял , возможно это мои пробелы в знаниях Си.
winipuh
Цитата(MaxiMuz @ Dec 2 2013, 16:14) *
по порядку:
1) Во первых Keil 4.7 не дает инлайнить функции.

blink.gif Да? Может у него что-то свое есть специфическое... типа __inline__ и т.п.?

Цитата(MaxiMuz @ Dec 2 2013, 16:14) *
2) Не ясен смысла записи GPIOB->BSRR = 1 << (LCD_WR1_pin + value?0:16);

BSRR - управляет 16-тью пинками GPIO.
Младшие 16 бит отвечают за установку пинов в "1", старшие 16 - за установку в "0".
Можно одной записью одновременно установить и сбросить несколько разных пинов...

А вышеприведенная (и, возможно, местами изъебисто написанная) конструкция буквально означает следующее:
Код
// n - номер GPIO-пина (от 0 до 15)
#define SET_PIN(n)   (0x0001 << (n))
#define CLR_PIN(n)   (0x0100 << (n))      //  ну или так — (0x0001 << ((n) + 16))

// какой-то конкретный пин
#define LCD_wr1_pin   4

// Функция для установки/сброса какого-то конкретного пина
static inline void LCD_wr1(const char value)
{
  if (value)
      GPIOB->BSRR = SET_PIN(LCD_wr1_pin);
   else
      GPIOB->BSRR = CLR_PIN(LCD_wr1_pin);
}
MaxiMuz
Цитата(winipuh @ Dec 2 2013, 15:34) *
blink.gif Да? Может у него что-то свое есть специфическое... типа __inline__ и т.п.?

вот этого я пока не знаю


Цитата(winipuh @ Dec 2 2013, 15:34) *
BSRR - управляет 16-тью пинками GPIO.
Младшие 16 бит отвечают за установку пинов в "1", старшие 16 - за установку в "0".
Можно одной записью одновременно установить и сбросить несколько разных пинов...
все мне известно


но конструкция
Код
static inline void LCD_wr1(const char value)
{
  if (value)
      GPIOB->BSRR = SET_PIN(LCD_wr1_pin);
   else
      GPIOB->BSRR = CLR_PIN(LCD_wr1_pin);
}
подразумевает вычисление или выбор по условию аргумента, а смысл макроса просто подстановка кода с уже готовым аргументом, вот этого я и добиваюсь
winipuh
Цитата(MaxiMuz @ Dec 2 2013, 23:09) *
но конструкция ... подразумевает вычисление или выбор по условию аргумента, а смысл макроса просто подстановка кода с уже готовым аргументом

Возможно Вы удивитесь, но на этапе компиляции не то-что инлайн — даже статические функции разворачиваются в конечное выражение не хуже макросов.
Пожалуйста:
Код
// СИ:
extern void my_func(unsigned int x);
static unsigned int my_value(unsigned int n) { return n ? 0x80 : 0x8000; }

void call_func(n) {
    my_func(value(4));    // на самом деле все это вычисляется на этапе компиляции
}

// ASM:
    stmfd    sp!, {r3, lr}
    mov    r0, #128        // и в итоге получается my_func(0x80) ( 0x80 = 128 )
    bl    my_func
    ldmfd    sp!, {r3, lr}
    bx    lr
    .size    call_func, .-call_func
    .ident    "GCC: (Sourcery CodeBench Lite 2013.05-23) 4.7.3"


Ну и наверное сочту своим долгом повторить еще раз то, что Вам уже говорил MrYuran
Цитата
Если абстрагироваться, то нужно и от пинов, и от портов, и от уровней.

А так вот что имеем:
RbitP(A,7); // вкл. на запись адреса A0=0
SbitP(A,5); // вкл.строба записи

Вопрос:
  1. Что будете делать, если Вам потребуется перенести пины с GPIOA на GPIOB?
  2. Что будете делать, если Вам потребуется использовать для строба записи на Pin5, а напр. Pin8?

Правильный вариант — это когда для этого нужно открыть некий h-файл и поправить там несколько строчек.
В вашем случае - править придется все места, где вызываются макросы SbitP, RbitP и т.д. И в этом случае (при всем уважении) Ваши макросы — просто какая-то обертка, чтобы писать меньше букв кода. Преимущество, мягко говоря, весьма сомнительное...
dxp
QUOTE (winipuh @ Dec 1 2013, 22:37) *
А зачем у аргумента функции квалификатор const? Тут же параметр не по ссылке передается, а по значению. blink.gif

Это стиль такой. Если не предполагается изменять аргумент внутри функции, то он фиксируется этим квалификатором. Для безопасности. Например, передаётся в функцию количество чего-нибудь, внутри оно используется несколько раз в разных местах. Если не зафиксировать, то имеется ненулевая вероятность, что в какой-то точке значение будет изменено под текущие локальные нужды (такое обычно бывает при редактировании сорцов, которые давно не трогали - внимания на вникание во все нюансы не хватает) без учёта того, что ниже по коду оно используется в предположении, что должно быть неизменным. Это полезная практика - фиксировать ещё на этапе проектирования.
MaxiMuz
Цитата(winipuh @ Dec 2 2013, 23:00) *
А так вот что имеем:
RbitP(A,7); // вкл. на запись адреса A0=0
SbitP(A,5); // вкл.строба записи

Вопрос:
  1. Что будете делать, если Вам потребуется перенести пины с GPIOA на GPIOB?
  2. Что будете делать, если Вам потребуется использовать для строба записи на Pin5, а напр. Pin8?

Правильный вариант — это когда для этого нужно открыть некий h-файл и поправить там несколько строчек.
В вашем случае - править придется все места, где вызываются макросы SbitP, RbitP и т.д.

Код
#define SbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BS##Nbit
вместо Port подставляем букву порта, второй параметр - номер пина. И ничего править не надо.
winipuh
Цитата(MaxiMuz @ Dec 3 2013, 08:30) *
Код
#define SbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BS##Nbit
вместо Port подставляем букву порта, второй параметр - номер пина. И ничего править не надо.

Вы меня не поняли! sad.gif

Все бывает в первый раз:
1) Пришлось корректировать разводку — как следствие поменялись функции пинов GPIO...
2) Пришлось добавить новую модель изделия. Немного другая разводка (другие функции пинов). Дерево исходников общее. Собираем либо под одно железо, либо под другое...

Имеем например: SbitP(A,5); // вкл.строба записи
Допустим теперь строб записи не на GPIOA(Pin_5) а на GPIOB(Pin_12) ... Как будете править исходники? Еще хуже — если править их придется кому-то другому... sad.gif
Придется пройтись по сишным файлам и везде строчку SbitP(A,5) поменять на SbitP(B,12). ... Можно, но сложно и некрасиво... А собирать из одного дерева под разное железо - задача в Вашем случае вообще непосильная... smile3046.gif

Ок? sm.gif
Макросы пишутся для того, чтобы обойти эту проблему... Ваши макросы (при все уважении) проблему эту не решают.
Теперь объясните — зачем же Вы их придумали?




Цитата(dxp @ Dec 3 2013, 08:24) *
Это стиль такой. Если не предполагается изменять аргумент внутри функции, то он фиксируется этим квалификатором.

Понял. sm.gif
MaxiMuz
Цитата(winipuh @ Dec 3 2013, 15:23) *
Имеем например: SbitP(A,5); // вкл.строба записи
Допустим теперь строб записи не на GPIOA(Pin_5) а на GPIOB(Pin_12) ... Как будете править исходники? Еще хуже — если править их придется кому-то другому... sad.gif
Придется пройтись по сишным файлам и везде строчку SbitP(A,5) поменять на SbitP(B,12). ... Можно, но сложно и некрасиво... А собирать из одного дерева под разное железо - задача в Вашем случае вообще непосильная... smile3046.gif

Ок? sm.gif
Макросы пишутся для того, чтобы обойти эту проблему... Ваши макросы (при все уважении) проблему эту не решают.
Теперь объясните — зачем же Вы их придумали?

Решение проблемы в моем случае - дефейнить название портов.
gpio.h
Код
/****************** Macros Set/Clear Bit of Port ***************************/
#define SbitP(Port,Nbit) _SbitP(Port,Nbit)
#define RbitP(Port,Nbit) _RbitP(Port,Nbit)
#define _SbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BS##Nbit
#define _RbitP(Port,Nbit)    GPIO##Port->BSRR=GPIO_BSRR_BR##Nbit
#define SmbitP(Port,mask)    GPIO##Port->BSRR=(mask)
#define RmbitP(Port,mask)    GPIO##Port->BSRR=(mask)<<16


main.c
Код
#define  LCD_ctrlPort A // Порт управляющих сигналов
#define  LCD_dataPort A // Порт шины данных
#define  LCD_dataShift 1 // Смещение шины данных от начала порта

#define  LCD_A0    7 // Выбор: Адрес A0=L/ Данные A0=H
#define  LCD_WR1     5

#include "gpio.h" //описание макросов ввода/вывода
....
....
void LCD_wrAdr (u8 Adr)
{
    SmbitP(LCD_dataPort,((Adr&0x0f)<<LCD_dataShift)); // выставляем на шину адрес
    RbitP(LCD_dataPort,LCD_A0); // вкл. на запись адреса A0=0
    SbitP(LCD_dataPort,LCD_WR); // вкл.строба записи
        ...
        ...
}

Единственное неудобство это описание пина двумя параметрами, но это все решаемо.
Сергей Борщ
Цитата(MaxiMuz @ Dec 11 2013, 09:36) *
Единственное неудобство это описание пина двумя параметрами, но это все решаемо.
"Мыши кололись, плакали, но продолжали жрать кактус"

Цитата(demiurg_spb @ Nov 29 2013, 14:52) *
Вы не до конца поняли намёк про макросы Аскольда Волкова.
Не поленитесь - погуглите да изучите.

Вот они.
ViKo
Не понимаю, зачем такие "муки творчества", если для установки или сброса бита в порте требуется всего-то (показано для STM32F2xx, но разница невелика):
Код
#define RSTO_ON()    GPIOC->BSRRH = (uint16_t)(1<<0)    //!< Выдать сброс (Low)
#define RSTO_OFF()    GPIOC->BSRRL = (uint16_t)(1<<0)    //!< Убрать сброс (High)
#define ANSYN_ON()    GPIOD->BSRRH = (uint16_t)(1<<2)    //!< ANSYN Low
#define ANSYN_OFF()    GPIOD->BSRRL = (uint16_t)(1<<2)    //!< ANSYN High

Сергей Борщ
Цитата(ViKo @ Dec 11 2013, 10:23) *
Не понимаю, зачем такие "муки творчества",
Чтобы не определять *_ON() и *_OFF() для каждой лапы. А если захочется ввод/вывод попереключать - еще два макроса дописывать? А если читать - еще один? А если изменить состояние на противоположное - еще один? В такой простыне уже на пяти выводах запутаешься, а часто их больше.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.