Работа с группами линий ввода-вывода на Си (без плюсов)

В процессе написания статьи по препроцессору Си я понял многое о его механизме работы и возможностях. Мне стало интересно, возможно ли реализовать групповые операции с линиями ввода-вывода, как это сделано тут с помощью препроцессора на обычном Си.

Задача: реализовать работу с произвольными группами линий ввода-вывода. Большая часть кода должна быть платформо-независимой. Платформо-зависимой должна быть только работа непосредственно с регистрами портов.
Сначала нужно придумать как определять порты. В принципе, можно это делать одной буквой (A, B, C и т.д.), а потом склеивать из нее идентификаторы регистров. Мне этот подход показался не очень гибким, и я предпочел составлять определение порта из всех его регистров плюс идентификатор для удобного сравнения портов в константных выражениях.
#define PORT_A PORTA, DDRA, PINA, 'A'
...
#define PORT_F PORTF, DDRF, PINF, 'F'

Далее нужен набор макросов определяющий набор операций с портом:
// запись значения в порт
#define __PORT_WRITE(VALUE, PORT, DDR, PIN, ID) (PORT) = (VALUE)
// установка битов в порту
#define __PORT_SET(VALUE, PORT, DDR, PIN, ID) (PORT) |= (VALUE)
// переключение битов
#define __PORT_TOGGLE(VALUE, PORT, DDR, PIN, ID) (PORT) ^= (VALUE)
// сброс битов
#define __PORT_CLEAR(VALUE, PORT, DDR, PIN, ID) (PORT) &= ~(VALUE)
// сброс битов по маске и установка
#define __PORT_CLEAR_SET(MASK, VALUE, PORT, DDR, PIN, ID) (PORT) = ((PORT) & ~(MASK)) | (VALUE)
// чтение выходного регистра
#define __PORT_READ(PORT, DDR, PIN, ID) (PORT)
// установка конфигурации
#define __PORT_CONFIGURATION(CONFIG, MASK, PORT, DDR, PIN, ID) do{if(CONFIG) (DDR) |= (MASK); else (DDR) &= ~(MASK); }while(0)
// чтение порта
#define __PORT_PIN(PORT, DDR, PIN, ID) (PIN)

Конфигурация порта определена так:
typedef enum
{
	PortAnalogIn = 0,
	PortIn = 0x00,
	PortPullUpOrDownIn = 0x00,
	PortOut = 0x01,
	PortAltOut = 0x01
}port_configuration_t;

Зачем в конфигурации столько значений, ведь в AVR есть только «вход» и «выход»? Это сделано для частичной совместимости с другими платформами, где PortAnalogIn не тоже самое, что просто PortIn.
А зачем все имена макросов начинать с двойного подчерка? В первую очередь потому, что эти макросы будут только для внутреннего использования. Будет ещё один уровень макросов для работы с портами, для того, чтоб препроцессор правильно разворачивал все параметры этих макросов.
Определять отдельные линии ввода-вывода будем так:
#define Pa0 0, PORT_A
...
#define Pa7 7, PORT_A

Потребуются еще два макроса для того, чтобы извлекать из определения линии ввода-вывода идентификатор порта и её номер бита:
#define __GET_PORT_ID(PIN_NUMBER, PORT, DDR, PIN, ID) (ID)
#define __GET_PIN_NUMBER(PIN_NUMBER, PORT, DDR, PIN, ID) (PIN_NUMBER)

На этом часть реализации зависимая от платформы закончилась. Теперь приступим к платформо-независимой части. Макросы второго уровня для портов:

#define GET_PORT_ID(...) __GET_PORT_ID(__VA_ARGS__)
#define GET_PIN_NUMBER(...) __GET_PIN_NUMBER(__VA_ARGS__)

#define PORT_WRITE(...) __PORT_WRITE(__VA_ARGS__)
#define PORT_SET(...) __PORT_SET(__VA_ARGS__)
#define PORT_TOGGLE(...) __PORT_TOGGLE(__VA_ARGS__)
#define PORT_CLEAR(...) __PORT_CLEAR(__VA_ARGS__)
#define PORT_CLEAR_SET(...) __PORT_CLEAR_SET(__VA_ARGS__)
#define PORT_READ(...) __PORT_READ(__VA_ARGS__)
#define PORT_CONFIGURATION(...) __PORT_CONFIGURATION(__VA_ARGS__)
#define PORT_PIN(...) __PORT_PIN(__VA_ARGS__)

Они реализованы как макросы с переменным числом аргументов, чтоб не зависеть от количества параметров, определяющих порт.
Эти макросы принимают любое число параметров, обратиться к ним можно по имени __VA_ARGS__, которое в результате макроподстановки превращается в список фактических параметров, разделённых запятыми.
Напишем макросы для работы с отдельными линиями ввода-вывода:
#define PIN_SET(NUMBER, ...) __PIN_SET(NUMBER __VA_ARGS__)
#define PIN_CLEAR(NUMBER, ...) __PIN_CLEAR(NUMBER __VA_ARGS__)
#define PIN_TOGGLE(NUMBER, ...) __PIN_TOGGLE(NUMBER __VA_ARGS__)
#define PIN_IS_SET(NUMBER, ...) __PIN_IS_SET(NUMBER __VA_ARGS__)
#define PIN_CONFIGURATION(CONFIGURATION, NUMBER, ...) __PIN_CONFIGURATION(CONFIGURATION, NUMBER __VA_ARGS__)

#define __PIN_SET(NUMBER, ...) PORT_SET(1 << (NUMBER), __VA_ARGS__)
#define __PIN_CLEAR(NUMBER, ...) PORT_CLEAR(1 << (NUMBER), __VA_ARGS__)
#define __PIN_TOGGLE(NUMBER, ...) PORT_TOGGLE(1 << (NUMBER), __VA_ARGS__)
#define __PIN_IS_SET(NUMBER, ...) ((PORT_PIN(__VA_ARGS__) & (1 << (NUMBER))) != 0)
#define __PIN_CONFIGURATION(CONFIGURATION, NUMBER, ...) PORT_CONFIGURATION(CONFIGURATION, 1 << (NUMBER), __VA_ARGS__)

Как видно, эти макросы также двухуровневые. В результате получилось что-то а-ля макросы Аскольда Волкова. Пользоваться ими так:
// сконфигурировать Pa1 на выход
PIN_CONFIGURATION(PortOut, Pa1);
// установить Pa1
	PIN_SET(Pa1);
// сбросить Pa1
	PIN_CLEAR(Pa1);
// переключить Pa1
	PIN_TOGGLE(Pa1);
// если на ножке высокий уровень
	if(PIN_IS_SET(Pa1))
		DoSomething();

Рассмотрим некоторые приемы работы со списками аргументов переменной длины, которые нам пригодятся в дальнейшем. В первую очередь, нам нужно уметь сосчитать параметры упакованные в __VA_ARGS__. Рассмотрим следующие макросы:
#define __COUNT_PARMS(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, LAST, ...) LAST
#define COUNT_PARMS(...) __COUNT_PARMS(0 __VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

COUNT_PARMS разворачивается в целочисленный литерал соответствующий количеству параметров. Максимальное количество параметров ограничено 16, но его легко увеличить. Также надо учитывать, что для пустого списка аргументов вернется 1, а не 0. Идея взята тут. Как это работает? Параметры в составе __VA_ARGS__ вставляются в начало списка целочисленных литералов идущих в обратном порядке в качестве параметров макросу __COUNT_PARMS, смещая их вперед. При этом в аргумент LAST попадает литерал соответствующий количеству параметров в __VA_ARGS__.
Как из списка __VA_ARGS__ получить первый параметр и все остальные параметры, кроме первого?
#define __GET_FIRST(FIRST, ...) FIRST
#define __GET_REST(FIRST, ... ) __VA_ARGS__
// получить первый параметр
#define GET_FIRST(FIRST, ...) __GET_FIRST(FIRST, __VA_ARGS__)
// получить остальные параметры, кроме первого
#define GET_REST(FIRST, ... ) __GET_REST(FIRST, __VA_ARGS__)

Думаю, особых пояснений уже не требуется, и так понятно.
Далее нам нужно решить как определять списки линий ввода-вывода, ведь определение одной линии — уже список из из номера линии и атрибутов порта, разделенный запятыми. Можно разграничивать определение линии в списке скобками "()", препроцессор их понимает и воспринимает всё, что в скобках как один параметр.
#define Group1 (Pa1), (Pa2), (Pa3), (Pb2), (Pb3), (Pb4)

Выглядит не плохо, только возникает другой вопрос: эти самые скобки никуда не деваются и включаются непосредственно в аргумент макроса, если его передать другому макросу «как есть», например, чтоб получить номер нашей линии с помощью макроса GET_PIN_NUMBER, то у нас ничего не получится потому, что значение в скобках по-прежнему будет одним параметров. От скобок нужно избавится.
#define __EAT_PARENTHESE(...) __VA_ARGS__
#define EAT_PARENTHESE(ARG) __EAT_PARENTHESE ARG

Если макросу EAT_PARENTHESE передать параметр со скобками, то он их благополучно «съест» и вернет список запакованных в скобки параметров. Если параметр без скобок — будет ошибка препроцессинга. Чтоб было ясно как это работает нужно вспомнить, что при вызове макроса макроподстановка выполняется два раза: первый — до вставки аргументов в тело макроса, для каждого аргумента в отдельности; второй — после вставки аргументов, для всего тела макроса. Так вот, после вставки аргумента в тело макроса получается выражение __EAT_PARENTHESE (что-то там в скобках), которое интерпретируется как вызов макроса __EAT_PARENTHESE, который получает список всего, что было в скобках. Скобки съедаются этим вызовом.
Двигаемся дальше. Попробуем вычислить маску для одного порта входящего в группу. Для этого нам нужно уметь организовать цикл по всем линиям из группы. Здесь и пригодится великий и ужасный Boost, а конкретно библиотека Boost.Preprocessor. Нужно что-то наподобие foreach для того, чтоб проитерировать группу, но foreach в Boost есть только для списков вида ((Pa1), ((Pa2), ((Pa3), ((Pa4))))) — preprocessor list, но оно не удобно — слишком много скобок, а выбранное представление в терминах Boost-а называется preprocessor array. По этому для итерации воспользуемся макросом BOOST_PP_REPEAT, а для доступа к элементам группы по индексу — BOOST_PP_ARRAY_ELEM.
#define FOR_EACH_IN_GROUP(MACRO_ITERATOR, ARGUMENT,...) \
BOOST_PP_REPEAT(GROUP_LENGTH(__VA_ARGS__), MACRO_ITERATOR, (ARGUMENT, __VA_ARGS__))

Этот макрос для каждой линии из группы вызывает макрос MACRO_ITERATOR и передаёт в него ARGUMENT и в придачу всё нашу группу линий, она тоже пригодится.
BOOST_PP_REPEAT принимает три параметра:
1 — число итераций — должно быть целочисленным литералом (1, 2, 3, 4 и т.д.), у нас уже есть GROUP_LENGTH(__VA_ARGS__), который возвращает длину группы.
2 — макрос, который вызывается в этом цикле.
3 — дополнительный параметр для MACRO_ITERATOR, в него упаковываются с помощью скобок ARGUMENT и вся группа линий целиком __VA_ARGS__.
Макрос MACRO_ITERATOR должен принимать три параметра (это требование буста):
#define MACRO_ITERATOR(LEVEL, INDEX, DATA) <тело макроса>

Здесь LEVEL — уровень вложенности итераций — BOOST_PP_REPEAT поддерживает до 3 уровней вложенности;
INDEX — текущий индекс;
DATA — данные, которые мы передали при вызове BOOST_PP_REPEAT, в нашем случае в там будет препроцессорный массив, первый элемент которого будет какой-то параметр, а остальные — все линии из переданной группы.
Теперь надо научится извлекать нужные данные из упакованного параметра DATA. Для этого уже есть макросы GET_FIRST и GET_REST. Конструкция
GET_FIRST DATA

вернет тот самый ARGUMENT из FOR_EACH_IN_GROUP. Скобки тут для вызова макроса не нужны так, как они уже содержатся в параметре DATA, макросы в нем уже развернуты, а склеивание токенов не применяется.
Затем нужно уметь извлекать из DATA идентификатор порта и позицию линии в порте для определенной линии из списка:
#define GET_DATA(MACRO, INDEX, DATA) MACRO(EAT_PARENTHESE(BOOST_PP_ARRAY_ELEM(INDEX, (COUNT_PARMS(GET_REST DATA), (GET_REST DATA)))))

Здесь MACRO — макрос, извлекающий нужные данный из указанной линии, это может быть GET_PORT_ID или GET_PIN_NUMBER.
INDEX — индекс линии в группе (спасибо, Кэп).
И вот после всех этих приготовлений, накоцец-то вычислим маски для порта:

#define GROUP_PORT_MASK_ITERATOR(LEVEL, INDEX, DATA) | ((GET_DATA(GET_PORT_ID, INDEX, DATA) == GET_FIRST DATA) ? \
                                                        (1 << GET_DATA(GET_PIN_NUMBER, INDEX, DATA)) : 0)

#define GROUP_PORT_MASK(PORT_ID,...) (0 FOR_EACH_IN_GROUP(GROUP_PORT_MASK_ITERATOR, PORT_ID, __VA_ARGS__))

В конечном итоге препроцессор преобразует эту конструкцию в такое страшненькое константное выражение:
(0 | ((('A') == 'B') ? (1 << (1)) : 0) | ((('A') == 'B') ? (1 << (2)) : 0) | ((('A') == 'B') ? (1 << (3)) : 0) | ((('B') == 'B') ? (1 << (2)) : 0) | ((('B') == 'B') ? (1 << (3)) : 0) | ((('B') == 'B') ? (1 << (4))

Из которого компилятор уже может вычислить маску.

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

По аналогии написан макрос для вычисления маски для выводимого в порт значения:
#define GROUP_VALUE_MASK_ITERATOR(LEVEL, INDEX, DATA) | ((GET_DATA(GET_PORT_ID, INDEX, DATA) == GET_FIRST DATA) ? \
                                                        (1 << INDEX) : 0)

#define GROUP_VALUE_MASK(PORT_ID,...) (0 FOR_EACH_IN_GROUP(GROUP_VALUE_MASK_ITERATOR, PORT_ID, __VA_ARGS__))

А так-же макрос для проверки наличия в группе линий из определённого порта:
#define GROUP_HAS_PORT_ITERATOR(LEVEL, INDEX, DATA) | (GET_DATA(GET_PORT_ID, INDEX, DATA) == GET_FIRST DATA)
#define GROUP_HAS_PORT(PORT_ID, ...) (0 FOR_EACH_IN_GROUP(GROUP_HAS_PORT_ITERATOR, PORT_ID, __VA_ARGS__))

Теперь самое интересное, ради чего всё и затевалось — вывод значений в группы линий. Рассмотрим установку бит в группе, остальное делается по анологии:

#define __GET_VALUE_INT2(DATA) GET_FIRST(EAT_PARENTHESE(GET_FIRST DATA))
#define __GET_PORT_ID_INT2(DATA) GET_FIRST(GET_REST(EAT_PARENTHESE(GET_FIRST DATA)))
#define __GET_GROUP_INT2(DATA) GET_REST DATA

#define PIN_MAP_ITERATOR(LEVEL, INDEX, DATA) if(__GET_PORT_ID_INT2(DATA) == GET_DATA(GET_PORT_ID, INDEX, DATA) && \
                                                  (__GET_VALUE_INT2(DATA) & (1 << INDEX))) \
                                                       CONCAT(group_set_result, __LINE__) |= (1 << GET_DATA(GET_PIN_NUMBER, INDEX, DATA));

#define PORT_SET_ITERATOR(LEVEL, INDEX, DATA) \
             if(GROUP_HAS_PORT(GET_PORT_ID(0, GET_PORT_BY_INDEX(INDEX)), GET_REST DATA) ){\
                 port_value_t CONCAT(group_set_result, __LINE__) = 0;\
                    FOR_EACH_IN_GROUP(PIN_MAP_ITERATOR, (GET_FIRST DATA,  GET_PORT_ID(0, GET_PORT_BY_INDEX(INDEX))), GET_REST DATA)\
                PORT_SET(\
                    CONCAT(group_set_result, __LINE__), \
                    GET_PORT_BY_INDEX(INDEX));\
            }

#define GROUP_SET(VALUE, ...) do{BOOST_PP_REPEAT(COUNT_PARMS(ALL_GPIO_PORTS), PORT_SET_ITERATOR, (VALUE, __VA_ARGS__)) }while(0)

Итак, GROUP_SET принимает в первом пераметре битовую маску в соответствии с которой нужно установить биты в группе, и группу линий ввода-вывода в качестве списка параметров переменной длинны. Весь макрос обернут в do{...}while(0) для удобства использования этого макроса в управляющих конструкциях (if, else, for, while и т.д.). Внутри для каждого имеющегося порта вызывается макрос PORT_SET_ITERATOR.
В макросе PORT_SET_ITERATOR мы проверяем, есть ли в данной группе линии из этого порта, заводим временную переменную типа port_value_t (этот тип соответствует разрядности порта) и отображаем в неё биты из входного значения с помощью макроса PIN_MAP_ITERATOR. Внутри PIN_MAP_ITERATOR биты из входного значения по одному копируются во временную переменную, с помощью битового ИЛИ.
Применить какие-то виды опртимизации, такие как групповое отображение последовательно идущих бит или бит имеющих одинаковую позицию во входном и в выходном значениях, как это сделано в С++ варианте, тут не удалось из-за ограничений препроцессора. Поиск идущих подряд битов из группы в реализации на препроцессоре будет иметь циклометрическую сложность порядка O(N^2), а с учетом удаления этих битов из исходной группы O(N^3), при этом общая сложность GROUP_SET будет O(N^5). Это ни в какие ворота не лезет, препроцессору не хватит памяти обработать группу уже из 7-8 линий.
Посмотрим, что получилось в результате:
GROUP_SET(PORTC, Group1);

PORTC — здесь взят в качестве «доступной переменной», которую нельзя соптимизировать. В результате этот вызов превращается в такую монструозную на первый взгляд (: и на второй тоже :) коонструкцию:
do{
// вывод в порт А
	if((0 | (('A') == ('A')) | (('A') == ('A')) | (('A') == ('A')) | (('B') == ('A')) | (('B') == ('A')) | (('B') == ('A'))) )
	{ 
		port_value_t group_set_result26 = 0; 
		if(('A') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 0))) 
			group_set_result26 |= (1 << (1)); 
		if(('A') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 1))) 
			group_set_result26 |= (1 << (2)); 
		if(('A') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 2))) 
			group_set_result26 |= (1 << (3)); 
		if(('A') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 3))) 
			group_set_result26 |= (1 << (2)); 
		if(('A') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 4))) 
			group_set_result26 |= (1 << (3)); 
		if(('A') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 5))) 
			group_set_result26 |= (1 << (4)); 
			
		((*(volatile uint8_t *)((0x1B) + 0x20))) |= (group_set_result26); 
	} 
// вывод в порт B
	if((0 | (('A') == ('B')) | (('A') == ('B')) | (('A') == ('B')) | (('B') == ('B')) | (('B') == ('B')) | (('B') == ('B'))) )
	{ 
		port_value_t group_set_result26 = 0; 
		if(('B') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 0))) 
			group_set_result26 |= (1 << (1)); 
		if(('B') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 1))) 
			group_set_result26 |= (1 << (2)); 
		if(('B') == ('A') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 2))) 
		group_set_result26 |= (1 << (3)); 
		if(('B') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 3))) 
			group_set_result26 |= (1 << (2)); 
		if(('B') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 4))) 
			group_set_result26 |= (1 << (3)); 
		if(('B') == ('B') && ((*(volatile uint8_t *)((0x15) + 0x20)) & (1 << 5))) 
			group_set_result26 |= (1 << (4)); 
		
		((*(volatile uint8_t *)((0x18) + 0x20))) |= (group_set_result26); 
	} 
// и так для всех имеющихся портов
…
}while(0);

Форматирование я сделал сам, на самом деле весь этот код занимает одну строчку. Да, да, если все эти макросы назвать непонятными именами и записывать их в одну строку, то этот код можно смело посылать на конкурс «most obfuscated C code» :) На самом деле пугаться не стоит — этот код по большей части состоит из константных выражений и всегда истинных/ложных условий. Он неплохо оптимизируется:
6c:	a8 9b       	sbis	0x15, 0	; 21
  6e:	02 c0       	rjmp	.+4      	; 0x74 <main+0x8>
  70:	92 e0       	ldi	r25, 0x02	; 2
  72:	01 c0       	rjmp	.+2      	; 0x76 <main+0xa>
  74:	90 e0       	ldi	r25, 0x00	; 0
  76:	a9 99       	sbic	0x15, 1	; 21
  78:	94 60       	ori	r25, 0x04	; 4
  7a:	aa 99       	sbic	0x15, 2	; 21
  7c:	98 60       	ori	r25, 0x08	; 8
  7e:	8b b3       	in	r24, 0x1b	; 27
  80:	98 2b       	or	r25, r24
  82:	9b bb       	out	0x1b, r25	; 27
  84:	ab 9b       	sbis	0x15, 3	; 21
  86:	02 c0       	rjmp	.+4      	; 0x8c <main+0x20>
  88:	94 e0       	ldi	r25, 0x04	; 4
  8a:	01 c0       	rjmp	.+2      	; 0x8e <main+0x22>
  8c:	90 e0       	ldi	r25, 0x00	; 0
  8e:	ac 99       	sbic	0x15, 4	; 21
  90:	98 60       	ori	r25, 0x08	; 8
  92:	ad 99       	sbic	0x15, 5	; 21
  94:	90 61       	ori	r25, 0x10	; 16
  96:	88 b3       	in	r24, 0x18	; 24
  98:	98 2b       	or	r25, r24
  9a:	98 bb       	out	0x18, r25	; 24

А если входное значение будет константой, так вообще замечательно:
6c:	8b b3       	in	r24, 0x1b	; 27
  6e:	8e 60       	ori	r24, 0x0E	; 14
  70:	8b bb       	out	0x1b, r24	; 27
  72:	88 b3       	in	r24, 0x18	; 24
  74:	8c 61       	ori	r24, 0x1C	; 28
  76:	88 bb       	out	0x18, r24	; 24

По аналогии с GROUP_SET пишутся макросы GROUP_CLEAR, GROUP_SET, GROUP_WRITE и GROUP_CONFIGURATION. Так-же есть макрос GROUP_READ_PIN, который читает значение с ножек группы, он реализован немного по другому, но с использованием той-же техники.
При использовании этих макросов, надо помнить, что при каждом их вызове тело макроса целиком подставляется в место вызова. Для вывода констант это хорошо — вывод константы в пару портов зачастую дешевле вызова функции. А вот при выводе переменных это может привести к раздутию кода. Поэтому для интенсивно используемых групп иногда целесообразно оборачивать макросы в функции:

#define Group1 (Pa1), (Pa2), (Pa3), (Pb2), (Pb3), (Pb4)
...
static inline void Group1Set(uint8_t value)
{
	GROUP_SET(value, Group1);
}

Однако, умные компиляторы, умеющие делать common subexpression elimination, например IAR, могут это сделать самостоятельно.

Итоги

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

  • +7
  • 25 ноября 2011, 14:15
  • neiver
  • 1
Файлы в топике: C_GPIO.zip

Комментарии (91)

RSS свернуть / развернуть
В scmRTOS есть удобные макросы для работы с портами. Они явно по проще.
С группами еще более менее хорошо выглядит, но они редко требуются. И в таких случаях я это делаю явно, чтоб не потерять читабильность кода.
0
  • avatar
  • a9d
  • 25 ноября 2011, 14:33
<code>
Рассмотрим некоторые приемы работы со списками аргументов переменной длины...
Тут вроде как тег «code» закрыться должен был?
0
Спасибо. Поправил.
0
Интересная статья. Детали пока еще не все понял, но буду разбираться.
Спасибо.
0
Офигеть, однако. O_O +1.
0
  • avatar
  • _YS_
  • 25 ноября 2011, 17:20
Спасибо за статью +1.

Правда, после прочтения у меня сложилось двойственное впечатление.

Использовать подобную реализацию просто и удобно. Благодаря хорошему поэтапному описанию реализации макросов – все, вроде, понятно.

Но, если бы я встретил подобную библиотеку макросов в чужом коде, без подробного описания, я бы потратил кучу времени на «декодирование» рализации, и высказал бы много «теплых» слов в адрес автора :)
+1
Ну, очевидно же, что это скорее экспонат, чем рабочий код. :) Но все равно впечатляет.
0
Да я понимаю :)
Вообще, подход вполне применимый, имеет свои плюсы. ИМХО, здесь главное «вовремя остановиться».
0
Отлично. И на сей раз я скажу — «как раз вовремя!» :)
0
  • avatar
  • Vga
  • 25 ноября 2011, 23:16
ну и зачем такой изврат? изврат ради изврата? Да даже сам, после полугода не вспомнишь, как это работает.
+1
У Вас были когда нибудь проекты, где, например, сегменты светодиодного индикатора хаотично распределены по двум-трём портам. А если такой индикатор в проекте не один и еще есть матричная клавиатура и черти-что еще? Сгруппировать линии удобным(для программиста) образом по портам нельзя, например, из-за разводки платы и использования некоторых линий с их альтернативными функциями(USART, SPI, ADC, TIMER и т.д.).
У Вас были такие проекты?
Как Вы решали обозначенную задачу?
Поделитесь опытом, мне очень интересно.
0
Да были. Из опыта скажу что проще поставить hc595 на этот индикатор, он стоит 10р, и зависти его на SPI. И если надо подключить больше индикаторов, то добавляем в сеть еще микросхемы эти. Ну зачем мучиться сейчас с динамической индикацией.
Матричную клавиатуру можно опрашивать не так быстро, чтобы делать оптимизированный код с дикими макросами.
Очень сложно это, не читабельно. И если ваш код будет поддерживать кто то или дорабатывать — переносить, то ему можно не позавидовать (как писали выше).
Да в принципе главное это решение задачи за определенное время, а пути ее решение дело вкуса каждого.
+1
Не всегда задачу можно решить параллельным регистром сдвига. Как Вы, например, объедините в удобный порт раскиданную хаотично двунаправленную шину? Так что метод хоть и плохочитаемый, но вполне имеет место быть…
0
Не так уж и часто приходица собирать шину из беспорядочно разбросанных ножек, мягко говоря. А если уж действительно нужно, стоит написать функцию.
Так или иначе, всё лучше, чем подобное месиво.
0
Никто не заставляет в него зарываться. А на top-level сей макрос интуитивно понятен и если инженер внезапно говорит «бля, а на плате-то мы третий бит на C5 вывели, а не B2!» программест вместо «Ffffuuuuu~!» скажет «не проблема» и заменит "#define Group1 (Pa1), (Pa2), (Pa3), (Pb2), (Pb3), (Pb4)" на "#define Group1 (Pa1), (Pa2), (Pa3), (Pc5), (Pb3), (Pb4)", даже если макросы писал не он и не знает, как они внутри устроены.
0
Сам, разок-другой, примени подобную хрень… осбенно для «двунаправленной шины»… ))))) высокоскоростной… )))))) и оптимизма поубавится… ))))))))))
Академическая польза… несомненно есть…
Практическая… Возникают периодически подобные вопросы на разных форумах… но всеобщей радости и поддержки данная хрень ни в одном из случаев не получила… Изврат всё это…
0
Сам, разок-другой, примени подобную хрень… осбенно для «двунаправленной шины»… ))))) высокоскоростной… ))))))
Обязательно. Ну, кроме последнего варианта — там уже придется плясать с ассемблером. И это будет еще сложнее, неподдерживаемее и непортируемее, чем нутро этих макросов (кстати, как раз с портируемостью у них без проблем, но для портирования придется вкурить, как они устроены).
0
И я о том… )))))
Обычно с необходимостью подобного изврата выплывают начинающие… типа… «заутюжил, замахался, присмотрелся… облажался»… и «люди добрыя, не дайте помереть плате моейной бесценной, памагите жисть вдохнуть в девайс необыкновенный»… ))))))))))))))
0
У меня было такое. Я битовые маски и накладвал их.
Все получилось довольно просто.

Где нужно было это делать в цикле, сделал массивы с масками.
0
вижу только один +, начинающим разобраться с работой препроцессора
0
имел «удовольствие» работать с кодом, активно использующим подобные конструкции. автора удостоил бы расстрела…
0
  • avatar
  • bzzz
  • 04 января 2012, 18:31
Что же вас так возмущает? Это же «легко читаемый и сопровождаемый» С :)
0
о, и сюда вылез… это не си, а cpp. учи матчасть…
0
о, и сюда вылез…
Продолжаете комментировать собственные действия?
это не си, а cpp.
Препроцессор является неотъемлемой частью С, даже если в некоторых реализациях и реализован в виде отдельной программы.
учи матчасть…
Очередная разумная рекомендация, которой вы, почему-то, не соизволили придерживаться. Иначе вы бы знали, например, что первые четыре из восьми фаз трансляции С определенных в стандарте реализуются именно препроцессором.
0
продолжаю удивляться какерам… которые не в состоянии увидеть разницу между языком и пользователем языка, употребляющим его безумно.

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

которые обращаются на «вы», полагая, что это добавляет им ума.
0
О шедевр.

продолжаю удивляться какерам
Я смотрю ты все и всех причисляешь к хакерам.

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

Есть инструменты которые намного проще чем Си. Но я что-то сомневаюсь, что ты когда-то признаешь блочное программирование.
0
как только ты покажешь реальное ПО, несколько млн.строк, время жизни лет 10. а то очень много теоретиков развелось, которые думают о высоком (типа ООП) и пишут конструкции как в этом топике, не представляя какой это шлак…
0
Windows давно уже переписан на С++. Mac OS на Objective C.
0
ядро os/x на Objective C? ну-ну. windows — это, прямо скажем, не очень хороший пример. как минимум потому что код закрыт и оценить его качество весьма затруднительно.
0
Скачай WDK и дальше рассказывай о закрытости. Все необходимое для разработки драйверов открыто. Причем очень давно. Также, для удобства, сделана документация к этому «закрытому» коду.

ядро os/x на Objective C? ну-ну
Чистокровность так важна? Чистокровный линукс опорочен связью с асмом)
0
документация… матерь божия, какая наивность… повторюсь, что в таких условиях я бы не стал судить о качестве кода венды.

дело не в чистокровности, а в том, что ядро os/x написано на C.
0
ты говорил о «реальных» проектах. А теперь перепрыгнул на качество?

А ведь еще существует Java.
0
хорошо. переформулирую. ты не знаешь на чем написано ядро windows. ядро os/x написано на C — я это знаю (приходилось ковыряться в исходниках).

Java — да, существует. и ООП существует. и еще много чего существует. но только почему-то С++ был назван «правильным». для микроконтроллеров! и обоснование было — прямо как из книжки про С++ :)
0
Ядро Windows написано на С++. Скачай WDK и посмотри на это таинственное ядро.

Java — да, существует. и ООП существует. и еще много чего существует. но только почему-то С++ был назван «правильным».
Кроме C++ под МК есть еще .NET. Но он еще сырой.
Т.е. у С++ нет конкурентов.
0
WDK — это не ядро.

есть масса систем, написанных на С. то есть конкурент С++ есть.
0
В WDK содержится код ядра. Его можно посмотреть. Почитать его описание. И пошагово поотлаживать.

есть масса систем, написанных на С. то есть конкурент С++ есть.
Нет. Под МК это фактически единственный ООП язык.
0
при этом никакой необходимости в «таком» ООП на МК нет. штуки типа виртуальных методов легко делаются на Си. скажем униксы этим пользуются с незапамятных времен. а штуки типа автоматической чистки памяти в ОС/МК стараются категорически избегать. никаких хитрых типов с методами там не нужно. ну так зачем ООП на МК?
0
В микроконтроллере мало памяти и флеша. Так зачем там сдался Си? Ничего не напоминает?
На каждом форуме связанном с электроникой есть срач в стиле «ASM vs C», «C vs C++» и т.д.
0
затем, что программировать и поддержить ПО на нем на порядок проще чем на С. а вот С++ программирование/поддержка проще не становится, особенно системных задач.
0
Да. На асме ПО поддерживать легче.

А где пруфик на сложность поддержки и разработки?
0
как человек, написавший достаточно кода под RT11JS на MACRO, достаточно кода на асме под i386, немного кода на С++ и много кода на С, утверждаю такое.
0
А где объективные утверждения?

Так можно зарядить «у меня член до колен и пять дипломов». Поэтому <утверждение> это истинно.
0
Заявления типа «никаких хитрых типов с методами там не нужно», при том, что те же драйвера как раз и представляют собой «типы с методами» заставляют усомниться в том, что вы хотя бы отдаленно представляете себе, что такое ООП в его С++ инкарнации.
0
красочно представляю себе как ты «заставляешь» себя «усомниться». «те же драйвера» в униксе легко обходятся обычной структурой с указателями на функции. и для этого не нужен С++. я вроде бы выше уже писал, про достаточность простых решений, но ты не смог себя заставить об этом подумать.
0
«те же драйвера» в униксе легко обходятся обычной структурой с указателями на функции. и для этого не нужен С++.
Во-первых, от того, что они «легко обходятся» само по себе решение не перестает быть ООП решением, а драйвера не перестают быть объектами. Во-вторых, вы путаете «можно сделать» и «простое решение» (не говоря уже о том, что вы никак не уточняете для кого решение должно быть простым — для компилятора или для программиста).
но ты не смог себя заставить об этом подумать.
Не смог, потому что мне не нужно было заставлять, я это и без вас знаю. Другой вопрос, что вы так и не смогли заставить себя подумать над моим ответом.
0
решение, очевидно, должно быть простым для программиста: в первую очередь для того, который с этим *чужым* кодом столкнется через год, а автора уже расстреляли.

ps. только в особенно извращенном мозгу могла возникнуть мысль, что кого-то беспокоит сложность кода для компилятора… это нужно записать.
0
Ну тогда LLVM оцените :)
0
как только ты покажешь реальное ПО, несколько млн.строк,
ПО такого размера можно писать на С только в силу крайней необходимости (то есть при отсутствии выбора). А прикладное ПО такого размера на С можно писать только при категорическом отказе думать головой.
время жизни лет 10.
Всего-то?
а то очень много теоретиков развелось, которые думают о высоком (типа ООП) и пишут конструкции как в этом топике, не представляя какой это шлак…
Вы, со своими рассуждениями о «ПО несколько млн. строк» в опасной близости от тех, кого так гневно осуждаете.

P.S. При всем моем уважении и любви к системному и низкоуровневому программированию, меряться пиписками «млн строк» с прикладным софтом ему не приходится и близко. «Сколько волка ни корми, у слона все равно толще». Да и время жизни в десятки лет там скорее норма, чем исключение. И хотя некоторые системы писаны на языках, названия которых многие из молодых программистов встречали, разве что, в книжках (кобол, к примеру, или прогрес 4гл), то сколько-нибудь современный софт такого плана в подавляющем большинстве случаев пишется на чем-нибудь сугубо ООП-ном, типа смолтока, жабы или дотнета.
+2
опа. неожиданно RTOS для микроконтроллера оказалась «прикладным ПО»… сильный ход!
0
опа. неожиданно RTOS для микроконтроллера оказалась «прикладным ПО»… сильный ход!
Нет, она оказалась «реальное ПО, несколько млн.строк».
0
похоже ты слегка контекст потерял. эта фраза была сказана про «блочное ПО». некрасиво.
0
Это только заражается. Классические его представители LabView и Matlab.
0
здорово. и много ПО создается в Matlab? 50%? 90%? это просто один из многих подходов в автоматизации. для каких-то задач работает… но говорить «перейдут на блочное программирование» — мрак какой-то.
0
За ним будущее.
Как и за микроядерными ОС. Кстати твой любимый Linux не микроядерный.

В microsoft давно смекнули, что монолитные ядра скоро помрут и уже давно финансируют разработку своей микроядерной ОС.

Сколько есть микроядерных ОС?50%? 90%? ))
0
не стоит говорить о будущем — ты его не знаешь. и я тоже.

в microsoft уже 20 лет смекают, но что-то последнее время сливают все и всем подряд. так что их мнение вряд-ли может считаться особо важным.

разные «фанаты» давно эти микроядерные ос пихают везде где надо и не надо. история противостояния очень богатая. но пока что нет ничего, указывающего, что они возьмут реванш над модульными ос (монолитных давно уже нет).
0
Линукс не монолит? Это, что-то новенькое.

Монолиты не могут работать эффективно на многоядерных системах. Интел с амд уже выплюнулю на рынок процессоры для которых нет ОС. И это не придел. Интел грозится перейти рубеж в 200 ядер.
0
еще раз. не говори за будущее.

ps. ничего, что линуксы уже работают на системах с >1000 ядер? при правильном подходе — достаточно эффективно. будет что-то, работающее хотя бы в разы лучше — можно будет говорить. а пока демогогия.
0
Дай пруфик. Ядра это не процессоры.
0
0
это многопроцессорный компьютер.
0
что суть одно и то же. память там общая (хотя с разными задержками, из-за архитектуры).
0
Это ты пошутил?
0
нисколько. нет принципиальной разницы разнесены ядра на разные чипы или нет.
0
Т.е. 200 процессоров у которых 200 планок оперативки и двести шин… Это одно и тоже, что 1 процессор у которого 200 ядер и одна планка оперативки?

Там большая разница. Поэтому нихра вменяемого еще нет. Для этих процессоров пытаются склепать микроядерную ОС.
QNX хорошее решение, но она платная и чужая.
0
у любой современной «мультипроцессорной системы» в реальности не 1 процессор с 200 ядрами + планка памяти, а иерархия: узел, у которого несколько процессоров, в каждом 2-16 ядер со своей памятью и таких узлов много. все они могут обращаться к чужой памяти прозрачно — адресное пространство одно. но задержка обращения к «чужой» памяти в несколько раз выше. шин тоже много, ест-но.

qnx (и микроядры в целом) никакого преимущества в данном случае не имеют, абсолютно.
именно поэтому всякие sgi/ibm/hp/проч и не дергаются в их сторону.
0
похоже ты слегка контекст потерял. эта фраза была сказана про «блочное ПО».
Слов «блочное ПО» в вашем посте нет. А чтению мыслей, извините, не обучен.
некрасиво.
С вашей стороны — безусловно.
0
продолжаю удивляться какерам…
Похоже вам нравится удивляться самому себе.
которые не в состоянии увидеть разницу между языком и пользователем языка, употребляющим его безумно.
«Велик могучим..» Вы хоть сами поняли, что написали?
которые не в состоянии понять, что если простой инструмент позволяет так же эффективно решать задачу, то он предпочтительнее более сложного инструмента.
Точно. Более того, вы даже не в состоянии понять, что «простота» относится к использованию языка, а вовсе не написанию компилятора.
которые обращаются на «вы», полагая, что это добавляет им ума.
Ну что вы. Судя по вашим постам вы искренне верите, что «ума» добавляет неразборчивость в выражениях при общении с малознакомыми людьми.
0
по-сути скажи что-нибудь
0
Обязательно. Как только дождусь от вас сути.
0
ну тогда иди читай мои первые коменты…
0
Во-первых, не говорите мне, что мне делать и я не стану уточнять адрес, по которому вы можете отправиться.
Во-вторых, в ваших первых коментариях сути тоже нет. Считать «сутью» никак не обоснованные плевки в сторону использования возможностей языка довольно сложно.
0
скажи, не стесняйся. мой первый комент вполне простой и доступный — автора такого кода нужно расстреливать, код этот ужастный, лучше сразу стереть. а твой ответ какой был? по-существу или чисто пофлеймить?
0
Приходите расстреливайте. Город Энгельс, зелёный пер. 22, 14.
Я вас жду.
0
я? пусть тебя пользователи твоих макросов расстреливают :)
0
От чего же такой умный гуру решил затереться в толпу нашего необразованного «быдла»? Может он начеркает статейку «ПРАВИЛЬНУЮ», чтобы все мы криворукие поняли как правильно это делается??? Не в обиду, а в отомщение=)
0
скажи, не стесняйся.
У lleo есть для этого специальная страничка.
мой первый комент вполне простой и доступный — автора такого кода нужно расстреливать, код этот ужастный, лучше сразу стереть.
Это ваши эмоции, не более того. Сути в них нет.
а твой ответ какой был? по-существу или чисто пофлеймить?
Ничем не хуже вашего первого коментария.
0
А вообще хватит тут гадить. Мне на весь этот ваш бред уведомления на мыло приходят.
0
ok
0
Небольшой недостаток библиотеки открылся при написании программы под мегу162. Долго не мог заставить работать порт Е, пока не добавил в список всех портов (PORT_E). Попытка быстренько добавить автоматическое определение через #ifdef PORTx сломала конструкцию макросов. Не подскажете, что можно сделать для увеличения универсальности?
0
Надёжный способ только один — сделать развесистое дерево #if/#endif с выбором списка портов по типу выбранного устройства.
По типу такого:
github.com/KonstantinChizhov/Mcucpp/blob/dev/mcucpp/AVR/__port_def.h
Эта портянка сгенерирована скриптом на Питоне.
0
Так может и всю остальную портянку питоно-скриптом генерировать?
0
Прямо по месту.
0
Чтоб остальную портянку питоно-скриптом генерить нужно этот самый скрипт каждую компиляцию запускать. всё-таки есть разница между «один раз сгенерить заголовок» и «генерить исходник при каждой компиляции».
До второго я пока не дорос :)
0
При использовании этих макросов, надо помнить, что при каждом их вызове тело макроса целиком подставляется в место вызова. Для вывода констант это хорошо — вывод константы в пару портов зачастую дешевле вызова функции. А вот при выводе переменных это может привести к раздутию кода. Поэтому для интенсивно используемых групп иногда целесообразно оборачивать макросы в функции:
Я бы рекомендовал ВСЕГДА заворачивать макросы в функции, потом легко можно будет настроить включение инлайна (по крайней мере в GCC). Плюс, можно скрыть поглубже все эти страхомакросы, а наружу выставить заголовки низкоуровневых функций в h-файле.
За статью 5+, глубоко проработано и разложено по полочкам. Спасибо.
0
Приветствую! Не могли бы вы пояснить, как работает эта конструкция —

#define __PORT_CONFIGURATION(CONFIG, MASK, ODR, DDR, IDR, CR1, CR2, ID) do{if(CONFIG) (DDR) |= (MASK); else (DDR) &= ~(MASK); }while(0)
0
А это откуда и для какого контроллера? Stm8 видимо. Вообще это должен быть низкоуровневый макрос для конфигурации ножек порта.
Где
CONFIG — конфигурация ножки — перечисление со всеми возможными конфигурациями, например для STM32:

typedef enum Configuration
{
	PortAnalogIn = 0,
	PortIn = 0x04,
	PortPullUpOrDownIn = 0x08,
	PortOut = 0x03, // default Out is Out50Mhz
	PortOut10Mhz = 0x01,
	PortOut2Mhz = 0x02,
	PortOut50Mhz = 0x03,
	PortOpenDrainOut = 0x07,
	PortOpenDrainOut10Mhz = 0x05,
	PortOpenDrainOut2Mhz = 0x06,
	PortOpenDrainOut50Mhz = 0x07,
	PortAltOut = 0x0B,
	PortAltOut10Mhz = 0x09,
	PortAltOut2Mhz = 0x0A,
	PortAltOut50Mhz = 0x0B,
	PortAltOpenDrain = 0x0f,
	PortAltOpenDrain10Mhz = 0x0C,
	PortAltOpenDrain2Mhz = 0x0E,
	PortAltOpenDrain50Mhz = 0x0f
} port_configuration_t;

MASK — целое число соответствеющее по разрядности порту — маска для изменяемых ножек — конфигурация CONFIG должна быть установлена для ножек, которым соответствует единичный бит в MASK. Конфигурация ножек, которым соответствует нулевой бит в MASK не должна меняться.
ID — идентификатор порта, может потребоваться если порты неравноценны.
Остальные параметры — регистры порта.
Конструкция do{… }while(0) нужна для того, чтоб этот макрос был единым выражеием независимо от контекста использования.
0
Однако я дубинушка… ) Стал модифицировать под STM8, и влепил вам сей опус. Это конечно же AVR. Вот оно —
#define __PORT_CONFIGURATION(CONFIG, MASK, PORT, DDR, PIN, ID) do{if(CONFIG) (DDR) |= (MASK); else (DDR) &= ~(MASK); }while(0)
0
Получилась вот такая ботва под STM8 (IAR) —
#ifndef C_IO_PORTS_H
#define C_IO_PORTS_H

// Platform dependant declarations
#define PORT_A PA_ODR, PA_DDR, PA_IDR, PA_CR1, PA_CR2, 'A'
#define PORT_B PB_ODR, PB_DDR, PB_IDR, PB_CR1, PB_CR2, 'B'
#define PORT_C PC_ODR, PC_DDR, PC_IDR, PC_CR1, PC_CR2, 'C'
#define PORT_D PD_ODR, PD_DDR, PD_IDR, PD_CR1, PD_CR2, 'D'
#define PORT_E PE_ODR, PE_DDR, PE_IDR, PE_CR1, PE_CR2, 'E'
#define PORT_F PF_ODR, PF_DDR, PF_IDR, PF_CR1, PF_CR2, 'F'

typedef enum
{
	InFlt,              // Floating without interrupt
        InPullUp,           // Pull-up without interrupt
        InFltInt,           // Floating with interrupt
        InPullUpInt,        // Pull-up with interrupt
                  
        OutOpenDrain,       // Open drain output
        OutOpenDrainFast,   // Open drain output, fast mode
        OutPushPull,        // Push-pull output
        OutPushPullFast,    // Push-pull, fast mode

}port_configuration_t;

typedef uint8_t port_value_t;


#define __PORT_WRITE(VALUE, ODR, DDR, IDR, CR1, CR2, ID) (ODR) = (VALUE)
#define __PORT_SET(VALUE, ODR, DDR, IDR, CR1, CR2, ID)   (ODR) |= (VALUE)
#define __PORT_TOGGLE(VALUE, ODR, DDR, IDR, CR1, CR2, ID)(ODR) ^= (VALUE)
#define __PORT_CLEAR(VALUE, ODR, DDR, IDR, CR1, CR2, ID) (ODR) &= ~(VALUE)
#define __PORT_CLEAR_SET(MASK, VALUE, ODR, DDR, IDR, CR1, CR2, ID) (ODR) = ((ODR) & ~(MASK)) | (VALUE)
#define __PORT_READ(ODR, DDR, IDR, CR1, CR2, ID)         (ODR)
#define __PORT_CONFIGURATION(CONFIG, MASK, ODR, DDR, IDR, CR1, CR2, ID)\
          do{\
             if(CONFIG == InFlt)\
              {\
               (DDR) &= ~(MASK);\
               (CR1) &= ~(MASK);\
               (CR2) &= ~(MASK);\
              }\
             if(CONFIG == InPullUp)\
              {\
               (DDR) &= ~(MASK);\
               (CR1) |= (MASK);\
               (CR2) &= ~(MASK);\
              }\
             if(CONFIG == InFltInt)\
              {\
               (DDR) &= ~(MASK);\
               (CR1) &= ~(MASK);\
               (CR2) |= (MASK);\
              }\
             if(CONFIG == InPullUpInt)\
              {\
               (DDR) &= ~(MASK);\
               (CR1) |= (MASK);\
               (CR2) |= (MASK);\
              }\
             if(CONFIG == OutOpenDrain)\
              {\
               (DDR) |= (MASK);\
               (CR1) &= ~(MASK);\
               (CR2) &= ~(MASK);\
              }\
             if(CONFIG == OutOpenDrainFast)\
              {\
               (DDR) |= (MASK);\
               (CR1) &= ~(MASK);\
               (CR2) |= (MASK);\
              }\
             if(CONFIG == OutPushPull)\
              {\
               (DDR) |= (MASK);\
               (CR1) |= (MASK);\
               (CR2) &= ~(MASK);\
              }\
             if(CONFIG == OutPushPullFast)\
              {\
               (DDR) |= (MASK);\
               (CR1) |= (MASK);\
               (CR2) |= (MASK);\
              }\
            }while(0)
#define __PORT_PIN(ODR, DDR, IDR, CR1, CR2, ID) (IDR)

#define __GET_PORT_ID(PIN_NUMBER, ODR, DDR, IDR, CR1, CR2, ID)    (ID)
#define __GET_PIN_NUMBER(PIN_NUMBER, ODR, DDR, IDR, CR1, CR2, ID) (PIN_NUMBER)

#endif

Идея понята верно?
0
Идея понята верно. А вот реализацию можно сделать получше. Вместо восьми if-ов можно обойтись тремя, если правильно распределить значения в port_configuration_t. Идея такая: каждому регистру в port_configuration_t соответствует 1 бит.
if(CONFIG & DdrBit)
  DDR |= MASK;
else
  DDR &= ~MASK;

if(CONFIG & Cr1Bit)
	CR1 |= MASK;
else
	CR1 &= ~MASK;

if(CONFIG & Cr2Bit)
  CR2 |= MASK;
else
  CR2 &= ~MASK;

Я думаю, понятно.
0
Коль скоро автор этого делать не торопится, а ответ мне интересен, то…
Anatol1980:
Смешное недоразумение. Автор сначала ставит задачу что «с помощью препроцессора на обычном Си», а применяет макросы С99. Что делает ее неприменимой для целого пласта работающих под MISRA, т.к. «Продолжается работа над следующей редакцией стандарта, адаптированной к C99».
Уж не говорю, что с при работе с группами бит на портах не работают операциями |= &= без специальных мероприятий по обеспечению атомарности.
0
  • avatar
  • Vga
  • 09 января 2013, 17:45
А чем собственно С99 не обычный? Стандарт-же, причем не столь новый — 13 лет уже. Поддеррживается всеми уважающими себя компиляторами. А работающие под MISRA могут и внешний препроцессор использовать, если очень хочется.
На счёт атомарности, если нужно, то можно легко добавить код запрещающий-разрешающий прерывания в макросы __PORT_SET, __PORT_CLEAR и т.д. Это нужно только для AVR, на STM32 модификация порта и так атомарна.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.