Работа с GPIO
В этой статье я бы хотел рассказать вам про разные способы работы с GPIO-ножками контроллера, показать
способ который я выбрал для себя и объяснить, почему именно его я использую.
Обзор
Когда я начал программировать контроллеры на С (до этого был еще ассемблер), с ножками контроллеров я работал примерно так:
PORTB &= ~(1<<PB7);
И я начал искать более красивый способ работы с GPIO. Поиски привели меня к так называемому макросу
Аскольда Волкова.
Я аж пищал от восторга. Казалось — вот он святой грааль 🙂 Все ножки можно было объявить в одном файле таким красивым образом
#define LED PORTB, 1, H
on(LED);
off(LED);
Первое мое разочарование этим макросом было когда я делал индикацию на 7-сегментных индикаторах. Код не только выглядел весьма уродливо, но еще и тратил кучу машинного времени. Ведь каждая on и off — это 3 ассемблерных комманды, которые компилятор не оптимизирует, так как все регистры объявлены как volatile !
К примеру, цифра 8 рисуется с помощью макроса Аскольда так:
on(Segment_A); on(Segment_B); on(Segment_C); on(Segment_D);
on(Segment_E); on(Segment_F); on(Segment_G);
Мой метод
Немного подумав, я "изобрел" еще один метод, который и использую по сегодняшний день:
Объявляем ножку как
#define LED_PB BIT1
Работаем с этой ножкой:
PORTB |= LED_PB;
PORTB &= ~LED_PB;
Если мы изменим только номер ножки, но порт останется тем-же
#define LED_PB BIT5
Если-же мы изменим порт, то мы поменяем суффикс, к примеру,
#define LED_PD BIT5
Заменить PORTB на PORTD будет очень просто.
Несколько ножек порта можно включить одним махом:
PORTB |= LED1_PB | LED2_PB | LED3_PB | LED4_PB;
Произойдет совсем не то, что вы ожидали. Потому, как операция ~ имеет больший приоритет, чем |.
Правильный код будет выглядеть так
PORTB &= ~(LED1_PB | LED2_PB | LED3_PB | LED4_PB);
#define PORTB_CLR(x) PORTB &=~(x)
#define PORTB_SET(x) PORTB |=(x)
Дополнение
Добрый человек с ником miv поделился своими макросами для работы с ножками LPC2368. При желании, можно эти макросы можно модифицировать под любой другой процессор.
Комментарии, как всегда, приветствуются.
Если-же мы изменим порт, то мы поменяем суффикс, к примеру,
А что мешает дать и PORTB человеческое имя? скажем LED_PORT
Тогда смена порта будет делаться лишь в одном месте программы — в дефайне этого порта.
Неоднократно уже с этим сталкиваюсь — люди видят что у порта уже есть символическое имя (PORTB) и стремаются присвоить ему свое имя. У мну так еще с ассемблера сложилось, что я все системные имена сразу же переименовываю по своему.
Первая причина в том, что один порт может использоваться кучей разных функций — кроме светодиодов там будут весеть еще и кнопки и индикатор и софт-spi и еще черт-знает что. Получится огромная куча дефайнов портов. Помножим это на количество регистров работы с портом — для АВР это 3, а для LPC — уже 7. Получится немыслимый файл с определениями, часть которых прийдется держать в голове.
Вторая причина — светодиоды могут быть распередены между двумя портами. Прийдется делать LED_PORT1, LED_PORT2 итп это может привести к тому, что человек попытается зажечь свтодиод LED1 записав его в LED_PORT1, хотя он принадлежит LED_PORT2. Такие ошибки легко ловяся, но непрятны.
Третяя причина в том, что мы вносим лишний уровень абстракции между схемой и софтом — смотрим на схему, там написанно PORTB, смотрим в программу — там LED_PORT — прийдется еще и эту связь держать в голове.
Получается мы раздуваем количество синтаксической информации до огромных размеров, не сильно сокращая объем семантики.
«Ведь каждая on и off – это 3 ассемблерных комманды» — это почему?
Использую «Волковские» макросы очень давно и никогда они для AVR не давали трёх команд (ну если не -O0, конечно). Куча команд только на инверсию, но это архитектурное.
#include
#include «_useful_gpio.h»
#define LED PORTB,1,L
void tst_on() { on(LED); }
void tst_off() { off(LED); }
void tst_cpl() { toggle_pin(LED); }
.text
.global tst_on
tst_on:
cbi 56-0x20,1
ret
.global tst_off
tst_off:
sbi 56-0x20,1
ret
.global tst_cpl
tst_cpl:
in r24,56-0x20
ldi r25,lo8(2)
eor r24,r25
out 56-0x20,r24
ret
В новых кристаллах инвертировать можно записью в PIN и это реализовано в моей модификации макросов (лежат в avr-gcc порте scmRTOS и на электрониксе год-два назад выкладывались), в ней же уже используется один макрос для ножки
#define LED B,1,L
а нужные PORT/PIN/DDR подставляются в макросах ON/OFF/TOGGLE/ACTIVE/LATCH/DRIVER
>это почему?
Вы правы.
Эх не знал что такие макросы уже есть 🙂
Писали с корешем свои, для авр объявление выглядит вот так
#define Pin_SPI_SS B, 2, High, Off
// B — литера порта
// 2 — ид вывода
// High — уровень логической единицы (по сути инверсия)
// Off — наличие Pull-Up сопротивления
для арм
#define Pin_PWM2 A, 2, High, NoPullUp, NoFiltr, NoMultiDrive, PerA, Out, NoIT
// A — литера порта
// 2 — ид вывода
// High — уровень логической единицы (по сути инверсия)
// NoPullUp — наличие Pull-Up сопротивления
// NoFiltr, NoMultiDrive, PerA, Out, NoIT — режимы работы линии
Как говориться, все хорошее придумали до нас )
Это точно 🙂
Но мы и писали его в 2001, может плохо искали….
Немного модифицировал макросы с целью автоматической инициализации портов.
Теперь у меня (для MSP430) базовый макрос для ноги выглядит как P1,1,H (вместо полного P1OUT,,,) и появилось два новых макроса InitAsIn(pin_macro) и InitAsOut(pin_macro).
Хотел замутить более общий макрос init() и макроопределение ноги как port,dir,pin,active_level , то есть P1,OUT,1,H , но не прокатило.
Вместо OUT подставляет в процессе преобразования 0х0004, получается полная шляпа. Но и так гламурненько получается.
#define SYS_WDT P4,7,H
#define PWR_WDT P6,0,H
static __inline__ void WatchDogInit(void)
{
InitAsOut(PWR_WDT);
InitAsOut(SYS_WDT);
}
static __inline__ void ClearWatchDog(void)
{
toggle_pin(SYS_WDT);
}
static __inline__ void ClearPWRWatchDog(void)
{
toggle_pin(PWR_WDT);
}
Ещё групповые операции осталось добавить — и вообще красота.
Главное, чтобы единообразно.
Ага, я давно таким пользуюсь. У меня (на lpc17xx) это выглядит так:
SetPinProps(GSM_GPIO1, DIR_OUT, MODE_NONE, FUNCTION_00, false );
А вообще, меня иногда порывает написать какой-то жудко высокоуровневый(для эмбеда) модуль для инициализации ножек, в который можно будет писать что-то типа
IntPins(«Default:out; P1: 1-3:in; 5, 6, 7:in pullup; P2: 3-31: in pulldown»);
Над конкретным синтаксисом нужно еще подумать, но суть, думаю. ясна.