Arduino — Манипуляция портами (Port Manipulation)
Узнайте, как управлять выводами Arduino через три различных регистра (DDR, PORT, PIN).
Регистры портов
Регистры портов позволяют выполнять низкоуровневое и более быстрое управление выводами ввода/вывода микроконтроллера на плате Arduino. Микросхемы, используемые на платах Arduino (ATmega8 и ATmega168), имеют три порта:
B (цифровые выводы 8–13)
C (аналоговые входы)
D (цифровые выводы 0–7)
Каждый порт управляется тремя регистрами, которые также определены как переменные в языке Arduino. Регистр DDR определяет, является ли вывод входом (INPUT) или выходом (OUTPUT). Регистр PORT управляет состоянием вывода — HIGH или LOW, а регистр PIN считывает состояние входных выводов, настроенных как вход с помощью pinMode(). Карты распиновки микросхем ATmega8 и ATmega168 показывают расположение портов. Более новая микросхема ATmega328P полностью повторяет распиновку ATmega168.
Регистры DDR и PORT можно как записывать, так и считывать. Регистры PIN соответствуют состоянию входов и доступны только для чтения.
PORTD соответствует цифровым выводам Arduino 0–7
DDRD — регистр направления данных порта D — чтение/запись
PORTD — регистр данных порта D — чтение/запись
PIND — регистр входных выводов порта D — только чтение
PORTB соответствует цифровым выводам Arduino 8–13. Два старших бита (6 и 7) подключены к кварцевому резонатору и недоступны для использования
DDRB — регистр направления данных порта B — чтение/запись
PORTB — регистр данных порта B — чтение/запись
PINB — регистр входных выводов порта B — только чтение
PORTC соответствует аналоговым выводам Arduino 0–5. Выводы 6 и 7 доступны только на Arduino Mini
DDRC — регистр направления данных порта C — чтение/запись
PORTC — регистр данных порта C — чтение/запись
PINC — регистр входных выводов порта C — только чтение
Каждый бит этих регистров соответствует одному выводу; например, младший бит DDRB, PORTB и PINB относится к выводу PB0 (цифровой вывод 8). Для полного соответствия номеров выводов Arduino портам и битам смотрите схемы для вашей микросхемы: ATmega8, ATmega168. (Обратите внимание, что некоторые биты порта могут использоваться для других целей, помимо ввода/вывода; будьте осторожны, чтобы не изменить значения соответствующих им битов регистра.)
Примеры
Обращаясь к карте выводов выше, регистры PortD управляют цифровыми выводами Arduino 0–7.
Предупреждение
Следует отметить, что выводы 0 и 1 используются для последовательной связи при программировании и отладке Arduino, поэтому изменения этих выводов обычно следует избегать, если только они не нужны для функций последовательного ввода/вывода. Имейте в виду, что это может помешать загрузке программы или отладке.
DDRD — это регистр направления для порта D (цифровые выводы Arduino 0–7). Биты в этом регистре определяют, настроены ли выводы PORTD как входы или выходы, например:
DDRD = B11111110; // устанавливает выводы Arduino 1–7 как выходы, вывод 0 как вход
DDRD = DDRD | B11111100; // это безопаснее, так как устанавливает выводы 2–7 как выходы
// без изменения значения выводов 0 и 1, которые являются RX и TX
См. справочные страницы по побитовым операторам и The Bitmath Tutorial в Playground.
PORTD — это регистр состояния выходов. Например:
PORTD = B10101000; // устанавливает цифровые выводы 7, 5, 3 в HIGH
Однако вы увидите 5 вольт на этих выводах только в том случае, если выводы были настроены как выходы с помощью регистра DDRD или функции pinMode().
PIND — это переменная регистра входов. Она считывает все цифровые входные выводы одновременно.
Зачем использовать манипуляцию портами?
Вообще говоря, делать подобные вещи — не лучшая идея. Почему? Вот несколько причин:
Код гораздо сложнее отлаживать и поддерживать, и другим людям его намного труднее понять. Процессору требуется всего несколько микросекунд для выполнения кода, но вам может потребоваться несколько часов, чтобы понять, почему он работает неправильно, и исправить это! Ваше время ценно, верно? А время компьютера очень дёшево, измеряемое стоимостью электричества, которое вы ему подаёте. Обычно гораздо лучше писать код наиболее очевидным способом.
Код менее переносим. Если вы используете
digitalRead()иdigitalWrite(), гораздо проще написать код, который будет работать на всех микроконтроллерах Atmel, тогда как регистры управления и портов могут отличаться на каждом типе микроконтроллера.Гораздо проще вызвать непреднамеренные неисправности при прямом доступе к портам. Обратите внимание, что в строке
DDRD = B11111110;выше упоминается, что вывод 0 должен оставаться входным. Вывод 0 — это линия приёма (RX) последовательного порта. Было бы очень легко случайно заставить последовательный порт перестать работать, изменив вывод 0 на выход! Это было бы очень запутанно, когда вы внезапно не можете получать последовательные данные, не правда ли?
Итак, вы можете сказать себе: отлично, зачем тогда мне вообще использовать всё это? Вот некоторые положительные стороны прямого доступа к портам:
Вам может потребоваться возможность включать и выключать выводы очень быстро, то есть за доли микросекунды. Если вы посмотрите на исходный код в
lib/targets/arduino/wiring.c, вы увидите, чтоdigitalRead()иdigitalWrite()— это каждая примерно дюжина строк кода, которые компилируются в довольно много машинных инструкций. Каждая машинная инструкция требует один такт при частоте 16 МГц, что может складываться в критичных по времени приложениях. Прямой доступ к портам может выполнить ту же работу за гораздо меньшее число тактов.Иногда вам может потребоваться установить несколько выходных выводов в точности в один и тот же момент времени. Вызов
digitalWrite(10, HIGH);с последующимdigitalWrite(11, HIGH);заставит вывод 10 перейти в HIGH на несколько микросекунд раньше, чем вывод 11, что может запутать определённые чувствительные ко времени внешние цифровые схемы. В качестве альтернативы вы можете установить оба вывода в HIGH точно в один и тот же момент времени, используяPORTB |= B1100;Если у вас заканчивается память программ, вы можете использовать эти приёмы, чтобы уменьшить размер кода. Для одновременной записи нескольких аппаратных выводов через регистры портов требуется гораздо меньше байт скомпилированного кода, чем при использовании цикла for для установки каждого вывода отдельно. В некоторых случаях это может определить, поместится ли ваша программа во флэш-память или нет!