GPIO-библиотека для AVR

Однажды пришлось мне решать до ужаса простую задачу по "мудренизации" одного серийного устройства. Ну и получилось так, что из-за избытка свободного времени начал страдать всякой фигней, как это обычно бывает. Захотелось соорудить велосипед придумать как удобно и красиво дергать ногами (контроллера).
Этот пост поведает об еще одном решении такой тривиальной задачи, как макросы для ногодрыга.
Гуру тут будет очень скучно. Возможно заинтересует кого из новичков. Под катом кратенькое описание и исходники "скачать бесплатно".

Обычно для каждого проекта я писал свои макроопределения для задействанных пинов, как делают многие. Что-то на манер:


#define BUZZER_PORT PORTD
#define BUZZER_DDR DDRD
#define BUZZER_PIN 5


#define INIT_BUZZER() BUZZER_PORT &= ~(1<<BUZZER_PIN)\
                      BUZZER_DDR  |=  (1<<BUZZER_PIN)

#define ON_BUZZER()  BUZZER_PORT  |=  (1<<BUZZER_PIN)
#define OFF_BUZZER() BUZZER_PORT  &= ~(1<<BUZZER_PIN)


Но это начинало выглядеть весьма уныло, когда таких дефайнов ставало много. К тому-же, для каждой ножки набор определений мог быть весьма разным, в зависимости от того, требовалось ли перенастраивать ее направление (вход/выход) в процессе работы, производить опрос состояния и т.д.
Вообщем, надумал я, что было бы неплохо использовать какое-то более структурированное и универсальное решение.
Конечно же рассматривал и существующие решения как знаменитые макросы Аскольда Волкова во всех их ипостасях. Но привыкнуть к их сверхминимализму я не смог еще когда пытался юзать в первый раз. Глядел также в сторону макросов от Дениса Железнякова (aka ZiB). Вот именно они мне казались уже близкими к идеалу, но ZiB что-то никак не наведет порядок у себя на сайте, а его макросы для АВР затерялись в каких-то там аналах интернетов. Во всяком случае, я их не нашел, увы. А переделывать те, что он также написал для STM8 или других контроллеров как-то ломало.

Так что решил я попробовать написать собственный вариант. Тем-более, что свой велосипед удобнее и понятнее, потому что делаешь его с учетом своего же текущего уровня знаний и существующих задач. Да и развивать или адаптировать потом проще, а это однозначно понадобится, ибо не Atmel-ом единым…

Долго ли коротко, но набор макросов был написан. Особо подробно распыляться не буду, так как в комментариях есть примеры использования каждого из них и все должно быть понятным. Расскажу лишь кратко об некоторых особенностях и моих личных предпочтениях.
То, что получилось в итоге выглядит так (осторожно, портянка!):


#ifndef GPIO_
#define GPIO_

#include "GPIO_def.h"

#define HIGH 1
#define LOW 0
#define PULL_UP 1
#define HI_Z 0

/*===========================================================================*/
/*================Блок определений для инициализации пина====================*/
/*===========================================================================*/
/*
Формат назначения пина - (#define PIN_N PORT, BIT)
Формат инициализации - (INIT_PIN(PIN_N, DIRECTION, INIT_LEVEL))

например: #define OUT1 A, 0
          INIT_PIN(OUT1, OUT, HIGH);

          #define INPUT D, 7
          INIT_PIN(INPUT, IN, HI_Z);
*/

#define INIT_PIN(pin_settings, dir, lev) GPIO_INIT_PIN(pin_settings, dir, lev)

#define GPIO_INIT_PIN(port, pin, dir, lev) \
        GPIO_INIT_PIN_##dir(port, pin, lev)
//Настройка выхода:
#define GPIO_INIT_PIN_OUT(port, pin, lev) \
{\
  SetBitVal(PORT##port, pin, lev); \
  SetBit(DDR##port, pin); \
}
//Настройка входа:
#define GPIO_INIT_PIN_IN(port, pin, lev) \
{\
  SetBitVal(PORT##port, pin, lev); \
  ClearBit(DDR##port, pin); \
}
/*===========================================================================*/


/*===========================================================================*/
/*=================Блок определений для управления пином=====================*/
/*===========================================================================*/
/*
Формат - (PIN_[ACTION](PIN_N))

например: PIN_SET(OUT1);
          PIN_CLEAR(OUT1);
          PIN_VAL(INPUT, HI_Z);
*/

//Установка:
#define PIN_SET(pin_settings)\
        GPIO_PIN_SET(pin_settings)
#define GPIO_PIN_SET(port, pin)\
{\
  SetBit(PORT##port, pin); \
}

//Очистка:
#define PIN_CLEAR(pin_settings)\
        GPIO_PIN_CLEAR(pin_settings)
#define GPIO_PIN_CLEAR(port, pin)\
{\
  ClearBit(PORT##port, pin); \
}

//Инвертировать:
#define PIN_INV(pin_settings)\
        GPIO_PIN_INV(pin_settings)
#define GPIO_PIN_INV(port, pin)\
{\
  InvBit(PORT##port, pin); \
}

//Присвоить значение:
#define PIN_VAL(pin_settings, value)\
        GPIO_PIN_VAL(pin_settings, value)
#define GPIO_PIN_VAL(port, pin, value)\
{\
  SetBitVal(PORT##port, pin, value); \
}
/*===========================================================================*/


/*===========================================================================*/
/*==============Блок определений для опроса состояния пина===================*/
/*===========================================================================*/
#define PIN_IS_SET(pin_settings)\
        GPIO_PIN_IS_SET(pin_settings)
#define GPIO_PIN_IS_SET(port, pin)\
  (BitIsSet(PIN##port, pin))

#define PIN_IS_CLEAR(pin_settings)\
        GPIO_PIN_IS_CLEAR(pin_settings)
#define GPIO_PIN_IS_CLEAR(port, pin)\
  (BitIsClear(PIN##port, pin))
/*===========================================================================*/


/*===========================================================================*/
/*==============Блок определений для инциализации групы пинов================*/
/*===========================================================================*/
/*
Формат назначения пинов - (#define PINS PORT, Bit(BIT_N1)|Bit(BIT_N2)|Bit(BIT_N3))
Формат инициализации - (INIT_PINS(PINS, DIRECTION, INIT_LEVEL))

например: #define OUTS B, Bit(0)|Bit(1)|Bit(7)
          INIT_PINS(OUTS, OUT, HIGH);

          #define BUTTONS D, Bit(4)|Bit(5)|Bit(6)|Bit(7)
          INIT_PINS(BUTTONS, IN, PULL_UP);
*/

#define INIT_PINS(pins_settings, dir, lev) GPIO_INIT_PINS(pins_settings, dir, lev)

#define GPIO_INIT_PINS(port, pins, dir, lev) \
        GPIO_INIT_PINS_##dir(port, pins, lev)
//Настройка выхода:
#define GPIO_INIT_PINS_OUT(port, pins, lev) \
{\
  if(lev) \
  { \
   PORT##port |= (pins); \
  } \
  else \
  { \
   PORT##port &= ~(pins); \
  }; \
  DDR##port |= pins; \
}
//Настройка входа:
#define GPIO_INIT_PINS_IN(port, pins, lev) \
{\
  if(lev) \
  { \
   PORT##port |= (pins); \
  } \
  else \
  { \
   PORT##port &= ~(pins); \
  }; \
  DDR##port &= ~(pins); \
}
/*===========================================================================*/

#endif


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


//Назначаем ножку:
#define BUZZER D, 5

//Инициализация:
INIT_PIN(BUZZER, OUT, LOW);

//Ну а дальше в коде:
PIN_SET(BUZZER);   //Установка
PIN_CLEAR(BUZZER); //Очистка
PIN_INV(BUZZER);   //Инверсия


Ну и пример для входа:


#define BUTTON D, 5

INIT_PIN(BUTTON, IN, PULL_UP);

if(PIN_IS_CLEAR(BUTTON))
{
// do something
};

//Меняем тип входа:
PIN_VAL(BUTTON, HI_Z);


Если требуется поменять направление работы пина — поступаем так:


INIT_PIN(BUZZER, IN, HI_Z);


Иногда, также, случается, что нужно проинициализировать несколько пинов на порту одинаково. В таком случае, поочередная инициализация каждой ножки с помощью макросов будет проигрывать прямой записи значений в порт как по компактности кода, так и в оптимальности. Для этого есть макрос груповой инициализации:


//Неиспользуемые пины порта B
#define PORTB_NC_PINS   B, Bit(2)|Bit(5)|Bit(6)|Bit(7)

INIT_PINS(PORTB_NC_PINS, IN, PULL_UP);


Как видите, использование макросов уменьшает объем кода, вместе с тем, увеличивая читабельность. Так как программа частично комментирует сама себя. Да и переназначить ножку теперь сущий пустяк: просто поменять два символа, и не лопатить весь код.

Если вы заметили, то в самом начале кода есть строчка
#include "GPIO_def.h"

Это также сделано для удобства: мы просто назначаем в одном этом файле все пользовательские определения для ножек контроллера и не ищем их потом по всему проекту.

Пример «живиго» проекта для IAR-а тут. Там же и все упомянутые исходники.

Для тех, кто не знает что такое
SetBit
Bit(3)
или
BitIsClear
поясняю:
Это очень удобные в работе макросы для работы с битами, которые находятся у меня в файле service.h. Не помню уже кто автор, но я однажды выдрал их из чужого проекта и теперь постоянно пользую. Очень рекомендую.

Ну а на этом все. Спасибо, что дочитали.
  • +2
  • 03 января 2013, 16:01
  • DOOMSDAY
  • 1
Файлы в топике: TSOP_v0.zip

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

RSS свернуть / развернуть
У вас такой новогодний ник ))
А либа, что то не вижу в ней полезности. Хотя я далеко не гуру, мне кажется что для новичка лутше использовать широко принятые выражения имен портов. А вот когда уже что то выучеш, можна и либу загнать в прожект.
0
… заодно переписав старый прожект с нуля. Можно и так, конечно)))
В любом случае, спасибо за мнение. Тут у каждого свои вкусы и привычки.
0
Да у каждого, кто пишет дофига под МК, есть свой набор «отмычек». Ваша либа какраз начинающим в помощь, «старики» уже давно свои заделали…
0
Стандартный совет завернуть макросы в do/while:
#define GPIO_INIT_PIN_OUT(port, pin, lev) \
do {    \
    ... \
} while(0)
0
  • avatar
  • John
  • 03 января 2013, 17:53
//Присвоить значение:
#define PIN_VAL(pin_settings, value)
А по названию я ожидал, что оно вернет состояние пина (кстати, как раз такой функции и нет). Лучше назвать ее PIN_SET_VAL. И добавить PIN_GET_VAL.
Формат назначения пинов — (#define PINS PORT, Bit(BIT_N1)|Bit(BIT_N2)||Bit(BIT_N3))
Ошибка. Не то чтоб она тут была действительно критична, но все же.

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

Ну и последнее — константы по возможности желательно делать enum'ами, а не дефайнами. И стоило бы изначально предусмотреть побольше констант. Примерно как у neiver'а.
0
  • avatar
  • Vga
  • 03 января 2013, 21:47
А по названию я ожидал, что оно вернет состояние пина (кстати, как раз такой функции и нет).
Ну как же нет? PIN_IS_SET как раз это и сделает. Ну а по поводу названия я хотел написать более привычно _TOOGLE, но получается длинно и выбивается из общей картины. Вообщем, подозревал, что это может сбивать с толку.

Ошибка.
Поправил.

Сложности возникают при работе с группами
Это да, тут прийдется думать.

Ну и последнее — константы по возможности желательно делать enum'ами, а не дефайнами. И стоило бы изначально предусмотреть побольше констант. Примерно как у neiver'а.
Принято ко вниманию. За линк спасибо, сейчас погляжу.
0
Ну как же нет? PIN_IS_SET как раз это и сделает.
Да, если подумать — так и есть. Но из названия это не очень очевидно)
0
Смешное недоразумение. Автор сначала ставит задачу что «с помощью препроцессора на обычном Си», а применяет макросы С99. Что делает ее неприменимой для целого пласта работающих под MISRA, т.к. «Продолжается работа над следующей редакцией стандарта, адаптированной к C99».
Уж не говорю, что с при работе с группами бит на портах не работают операциями |= &= без специальных мероприятий по обеспечению атомарности.
0
Пардон, имеется в виду статья по приведенной Vga ссылке «где побольше констант».
0
Вот туда этот комментарий и утащи. Автор заметит и прокомментирует.
0
#define PORTB_NC_PINS   B, Bit(2)|Bit(5)|Bit(6)|Bit(7)
Надёжнее так:
#define PORTB_NC_PINS   B, (Bit(2)|Bit(5)|Bit(6)|Bit(7))
+1
Сначала хотел не согласиться, но потом понял, что Вы таки правы. Особенно, если использовать эти определения вне библиотеки.
0
Это точно, бардак у меня в блоге полнейший ))
Но есть поиск же, правда в данном случае он не помог, запись видать при переезде пехерелась. Нужно будет поискать в архиве.
Под авр я начинал писать их с корешем году в 2001-ом, поэтому и «перетаскиваю» их под все, что юзаю, вот только глупо сделал
под каждую платформу свой файл (поэтому и хрен найдешь), более правильнее наверное было использовать один файл.
Я их на электрониксе выкладывал кажется, но он сейчас лежит. Как найду отпишусь сюда…
0
  • avatar
  • ZiB
  • 04 января 2013, 17:17
Восстановил запись
макросы
+1
Спасибо, сохранил на всякий случай.
0
Файл из топика не грузится. :(
0
Да, какая-то хрень случилась. Сейчас перелью.
0
Пробуйте сейчас
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.