из каментов к 
http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-chast-2.html



То есть (по крайне мере, по моему) вместо
while(PORTB & (1 << MYPIN_DRIVE_LOCK))

гораздо приятнее писать
while(ControlBus.DriveLock)

Я так себе вообще написал удобные макросы, с помощью которых порты определяю так:
BEGIN_PORT_DEFINITION
	DEFINE_PORT_PIN(RowFeedback)
	DEFINE_PORT_PIN(RowForwarder)
	DEFINE_PORT_PIN(RowDataline)
	DEFINE_PORT_PIN(ColFeedback)
	DEFINE_PORT_PIN(ColForwarder)
	DEFINE_PORT_PIN(AnodeSwitch)
END_PORT_DEFINITION(Matrix, A)

инициализирую так:
	HalConfigurePin(Matrix,		RowFeedback,	IN,		PULLUP	);
	HalConfigurePin(Matrix,		RowForwarder,	OUT,			);
	HalConfigurePin(Matrix,		RowDataline,	OUT,			);
	HalConfigurePin(Matrix,		ColFeedback,	IN,		PULLUP	);
	HalConfigurePin(Matrix,		ColForwarder,	OUT,			);
	HalConfigurePin(Matrix,		AnodeSwitch,	IN,		Z	);

А юзаю примерно так:
	Matrix.Out->RowDataline = 1;
	ImpulseP(Matrix.Out->RowForwarder);
	Matrix.Out->RowDataline = 0;

	if(Matrix.In->RowFeedback)
	{
		//
		// Отсутствует отклик с RCM-шины. Нет ни одного RCM
		// или сбой в первом RCM
		// 

		PostErrorSound(POSTERROR_NO_RCM_FEEDBACK);
	}

ИМХО это выразительнее, чем побитовые опрерации, при равной производительности 
и компактности кода.


----

ОК, сейчас объясню.

Фишка в том, что в Си мы имеем возможность юзать bit-field’ы (кто не знает что 
это такое, ищите сами, я здесь объяснять не буду).

У нас порт это (обычно) 8 пинов и мы работаем с каждым пином дёргая 
соответствующие биты в соответствующих портах.

Ну а доступ к отдельным битам можно получать по разному: можно с помощью 
bitwise-операций, а можно с помощью bit-field’ов. Адекватному компилятору не 
должно быть никакой разницы, написали ли мы
    foo |= 2

или же у нас
    foo.SecondBitField = 1

он в обоих случаях генерирует один и тот же машинный код.

Так вот, есть у нас, скажем, PORTC, у которого первые три пина — выходы, на 
которых висят светодиоды, потом один пулап-вход и 2 высокоимп. входа.

Мы могли бы объявить 6 констант и работать с регистрами порта с помощью 
обычных побитовых операторов, примерно так:
    #define MY_PIN_RED_LED     0
    #define MY_PIN_YELLOW_LED  1
    #define MY_PIN_GREEN_LED   2
    #define MY_PIN_BUTTON      3
    #define MY_PIN_AUX1        4
    #define MY_PIN_AUX2        5

    ...

    DDRC = 0;
    // Делаем первые три пина выходами
    DDRC |= ((1 << MY_PIN_RED_LED) | (1 << MY_PIN_YELLOW_RED) | (1 << MY_PIN_GREEN_LED));

    // Включаем подтяжку на нужном пине
    PORTC = 1 << MY_PIN_BUTTON;
    ...

    // Зажигаем красный и зелёный сиды:
    PORTC |= ((1 << MY_PIN_RED_LED) | (1 << MY_PIN_GREEN_LED));

    // Проверяем вход последнего пина:
    if(PINC & (1 << MY_PIN_AUX2)) ...

но могли бы объявить такую структуру:
    typedef struct
    {
        char RedLed:1;
        char YellowRed:1;
        char GreenLed:1;
        char Button:1;
        char Aux1:1;
        char Aux2:1;
    } MY_PORT_PINS;

и делать то же самое, но кастуя тип регистров к этой структуре:
    // Делаем первые три пины выходами
    ((MY_PORT_PINS)DDRC).RedLed = 1;
    ((MY_PORT_PINS)DDRC).YellowRed = 1;
    ((MY_PORT_PINS)DDRC).GreenRed = 1;

    // Включаем подтяжку на нужном пине
    ((MY_PORT_PINS)PORTC).Button = 1
    ...

    // Зажигаем красный и зелёный сиды:
    ((MY_PORT_PINS)PORTC).RedLed = 1;
    ((MY_PORT_PINS)PORTC).GreenLed = 1;

    Проверяем вход последнего пина:
    if(((MY_PORT_PINS)PINC).Aux2) ...

Как видите, здесь структура MY_PORT_PINS выступает чем-то вроде контейнера для 
бит-фиелдов и отражает реальную структуру порта.

Это выглядит чуть лучше, но всё ещё плохо: куча кастования. Кроме того, этот 
код вообще не скомпилируетя. По крайней мере AvrGCC отказался кастовать PORTC 
к структуре. Да и у нас тут везде всё те же PORTC, PINC, DDRC. Если мы захотим 
сменить порт на B, это что же нам, везде менять? Даже если мы заюзам макросы, 
придётся использовать три макроса (ну и менять соответственно 3 строчки).

Негоже! :)

Поэтому окончательное решение состоит в том, чтобы создать глобальную 
переменную для всего порта, имя которой будет отражать предназначение порта. 
Типом этой глобальной переменной будет структура с трёмя полями-указателями на 
PORTX, PINX и DDRX.

Важно (!!!) поставить квалификатор const в объявления указателей. 
Использование константных указателей компилятор оптимизирует, поэтому никакой 
разницы в коде между предыдущим примером не будет, зато внешне всё будет 
выглядеть красивее:

(Ахтунг! Я советую вам чем раньше, тем лучше подробно разобраться с 
CV-квалификаторами. Крайне важно понимать, чем указатели на константу (const 
int * a) отличаются от константных указателей (int * const a), очень важно, 
чтобы ваш мозг не взорвался когда вы увидите сочетание const- и volatile- 
квалификаторов (это абсолютно нормальное сочетание, на самом деле))

    typedef struct
    {
        char RedLed:1;
        char YellowRed:1;
        char GreenLed:1;
        char Button:1;
        char Aux1:1;
        char Aux2:1;
    } MY_PORT_PINS;

    typedef struct
    {
        MY_PORT_PINS* const In;
        volatile MY_PORT_PINS* const Out;
        MY_PORT_PINS* const DDR;
    } MY_PORT_CONTROL_BUS ControlBus = {
                                            (MY_PORT_PINS*)&PINC,
                                            (MY_PORT_PINS*)&PORTC,
                                            (MY_PORT_PINS*)&DDRC,
                                       };

    // Всё, теперь доступ к битам регистра PIN порта С осуществляетсчя так:
    //         ControlBus->In.xxxxxx
    // доступ к битам регистра PORT порта C, так:
    //         ControlBus->Out.xxxxxx
    // доступ к битам регистра DDR порта C, так:
    //         COntrolBus->DDR.xxxxxx
    // где xxxxx -- логический пин, он же поле из структуры MY_PORT_PINS,
    //              например GreenLed.

Чтобы для каждого совего порта не писать такую громадину из сткрутур, я создал 
следующие три макроса:
    #define BEGIN_PORT_DEFINITION  typedef struct {
    #define DEFINE_PORT_PIN(name) char name:1;
    #define END_PORT_DEFINITION(portname, hardport) } SYSPORTPINS_##portname; \
                                            typedef struct \
              { \
               SYSPORTPINS_##portname* const In; \
               volatile SYSPORTPINS_##portname* const Out; \
               SYSPORTPINS_##portname* const DDR; \
              } SYSPORT_##portname; \
              SYSPORT_##portname portname = {(SYSPORTPINS_##portname*)&PIN##hardport,     

(SYSPORTPINS_##portname*)&PORT##hardport, (SYSPORTPINS_##portname*)&DDR##hardport};

С применением этих макросов вместо такого объявления:
    typedef struct
    {
        char RedLed:1;
        char YellowRed:1;
        char GreenLed:1;
        char Button:1;
        char Aux1:1;
        char Aux2:1;
    } MY_PORT_PINS;

    typedef struct
    {
        MY_PORT_PINS* const In;
        volatile MY_PORT_PINS* const Out;
        MY_PORT_PINS* const DDR;
    } MY_PORT_CONTROL_BUS ControlBus = {
                                            (MY_PORT_PINS*)&PINC,
                                            (MY_PORT_PINS*)&PORTC,
                                            (MY_PORT_PINS*)&DDRC,
                                       };

мы можем писать так:
    BEGIN_PORT_DEFINITION
        DEFINE_PORT_PIN(RedLed)
        DEFINE_PORT_PIN(YellowRed)
        DEFINE_PORT_PIN(GreenLed)
        DEFINE_PORT_PIN(Button)
        DEFINE_PORT_PIN(Aux1)
        DEFINE_PORT_PIN(Aux2)
    END_PORT_DEFINITION(ControlBus,C)

Осталось только сделать удобное конфигурирование портов. 

Как известно, если в DDR бит установлен, то пин — выход, в противном случае вход.
 Если порт выход, то бит в регистре PORT это его состояние, если же вход — то 
бит в регистре PORT определяет, включена подтяжка или нет.

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

Сам макрос (+ прилагающиеся к нему константы) выглядит так:
    #define __HALCONF_   0
    #define __HALCONF_OUT   1
    #define __HALCONF_IN  0
    #define __HALCONF_PULLUP 1
    #define __HALCONF_Z   0

    #define HalConfigurePin(__port, __pin, __in_out, __pullup_val) __port.DDR->__pin = __HALCONF_##__in_out;

__port.Out->__pin = __HALCONF_##__pullup_val

Соответственно, код для конфигурирования наших 6 пинов будет выглядеть так:
    HalConfigurePin(ControlBus, RedLed        , OUT ,        );
    HalConfigurePin(ControlBus, YellowRed     , OUT ,        );
    HalConfigurePin(ControlBus, GreenLed      , OUT ,        );
    HalConfigurePin(ControlBus, Button        , IN  , PULLUP );
    HalConfigurePin(ControlBus, Aux1          , IN  , Z      );
    HalConfigurePin(ControlBus, Aux2          , IN  , Z      );

Ну и первоначальный код соответственно будет выглядеть так:
    BEGIN_PORT_DEFINITION
        DEFINE_PORT_PIN(RedLed)
        DEFINE_PORT_PIN(YellowRed)
        DEFINE_PORT_PIN(GreenLed)
        DEFINE_PORT_PIN(Button)
        DEFINE_PORT_PIN(Aux1)
        DEFINE_PORT_PIN(Aux2)
    END_PORT_DEFINITION(ControlBus,C)

    ...

    HalConfigurePin(ControlBus, RedLed        , OUT ,        );
    HalConfigurePin(ControlBus, YellowRed     , OUT ,        );
    HalConfigurePin(ControlBus, GreenLed      , OUT ,        );
    HalConfigurePin(ControlBus, Button        , IN  , PULLUP );
    HalConfigurePin(ControlBus, Aux1          , IN  , Z      );
    HalConfigurePin(ControlBus, Aux2          , IN  , Z      );

    // Зажигаем красный и зелёный сиды:
    ControlBus->Out.RedLed = 1;
    ControlBus->Out.GreenLed = 1;

    //Проверяем вход последнего пина:
    if(ControlBus->In.Aux2) ...


----

К конкретному порту идёт привязка в макросе END_PORT_DEFINITION. Там вторым 
параметром указывается буковка. Я ступил и забыл написать в этом 
посте-примере, но в самом первом посте (где Matrix) это есть.

А в этом посте, если у тебя есть возможность, поправь
 END_PORT_DEFINITION
 на
 END_PORT_DEFINITION(ControlBus, X)
 где X — поставь букву любого порта, к которому душа лежит :)

