AVR. Работа с портами ввода-вывода. Практика. Учебный курс. Как работать с битами. Макроопределения Операции с битами регистра pinb в avr

Управление портами в AVR GCC. Регистры DDRx и PORTx.
Представление чисел. Побитные операции.
Функция задержки. Безусловный переход в программе.

Порты микроконтроллера - это устройства ввода/вывода, позволяющие микроконтроллеру передавать или принимать данные. Стандартный порт микроконтроллера AVR имеет восемь разрядов данных, которые могут передаваться или приниматься параллельно. Каждому разряду (или биту) соответствует вывод (ножка) микроконтроллера. Ножки микроконтроллера также называют пинами. Для обозначения портов используются латинские буквы А, В, С и т.д. Количество портов ввода/вывода варьируется в зависимости от модели микроконтроллера.

Любой порт микроконтроллера можно сконфигурировать как вход или как выход. Для того чтобы это сделать, следует записать в соответствующий порту регистр DDRx необходимое значение. Кроме того, как вход или выход можно сконфигурировать отдельно любой вывод (пин) порта. В любом случае, хотите вы сконфигурировать весь порт или отдельный вывод, вам необходимо будет работать с ригистрами DDRx.

DDRx - регистр направления передачи данных. Этот регистр определяет, является тот или иной вывод порта входом или выходом. Если некоторый разряд регистра DDRx содержит логическую единицу, то соответствующий вывод порта сконфигурирован как выход, в противном случае - как вход. Буква x в данном случае должна обозначать имя порта, с которым вы работаете. Таким образом, для порта A это будет регистр DDRA, для порта B - регистр DDRB и т. д.

Используя AVR GCC , записать в необходимый регистр то или иное значение можно одним из следующих способов.

Для всего порта сразу.

DDRD = 0xff;

Все выводы порта D будут сконфигурированы как выходы.

0xff - шестнадцатиричное представление числа ff, где 0x является префиксом, используемым для записи шестнадцатиричных чисел. В десятичном представлении это будет число 255, а в двоичном виде оно будет выглядеть как 11111111. То есть во всех битах регистра DDRD будут записаны логические единицы.

В AVR GCC для представления двоичных чисел используется префикс 0b. Таким образом, число 11111111 должно представляться в программе как 0b11111111. Мы можем записать предыдущую команду в более читабельном виде.

DDRD = 0b11111111;

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

Для того чтобы сконфигурировать все выводы порта D как входы, следует записать во все биты регистра DDRD логические нули.

DDRD = 0x00;

В регистр DDRD можно записать и другие числа. Например:

DDRD = 0xb3;

0xb3 - шестнадцатиричное представление числа 179. В двоичном виде оно будет выглядеть как 10110011. То есть часть выводов порта D будет сконфигурирована как выходы, а часть - как входы.

PD0 - 1 (выход)
PD1 - 1 (выход)
PD2 - 0 (вход)
PD3 - 0 (вход)
PD4 - 1 (выход)
PD5 - 1 (выход)
PD6 - 0 (вход)
PD7 - 1 (выход)

Чтобы сконфигурировать отдельно вывод PD2 как вход, нам необходимо в соответствующий бит регистра DDRD записать 0. Для этого применяют следующую конструкцию.

DDRD &= ~(1
В данном случае результат сдвига единицы на две позиции влево инвертируется с помощью операции побитного инвертирования, обозначаемой значком "~ ".

При инверсии мы получаем вместо нулей единички, а вместо единичек - нули. Эта логическая операция иначе называется операцией НЕ (английское название NOT).

Таким образом, при побитном инвертировании 00000100 мы получаем 11111011. (Подробнее о работе с числами в микроконтроллере см. во врезке ниже.)

Получившееся число с помощью операции побитного логического умножения & умножается на число, хранящееся в регистре DDRD, и результат записывается в регистр DDRD.

При логическом умножении 0*0=0, 0*1=0, 1*1=1 . Операцию логического умножения иначе называют операцией И (английское название AND).

То есть сдвинутая нами влево на две позиции единичка превращается при инвертировании в ноль и умножается на соответствующий бит, хранящийся в регистре DDRD. При умножении на ноль мы получаем ноль. Таким образом, бит PD2 становится равным нулю.

После того как направление передачи данных у порта сконфигурировано, можно присвоить порту значение, которое будет храниться в соответствующем регистре PORTx .
PORTx - регистр порта, где x обозначает имя порта.

Если вывод сконфигурирован как выход, то единичка в соответствующем бите регистра PORTx формирует на выводе сигнал высокого уровня, а ноль - сигнал низкого уровня.

Если же вывод сконфигурирован как вход, то единичка в соответствующем бите регистра PORTx подключает к выводу внутренний подтягивающий pull-up резистор, который обеспечивает высокий уровень на входе при отсутствии внешнего сигнала.

Установить "1" на всех выводах порта D можно следующим образом.

PORTD = 0xff;

А установить "0" на всех выводах порта D можно так.

PORTD = 0x00;

К каждому биту регистров PORTx можно обращаться и по отдельности так же, как в случае с регистрами DDRx.

Например, команда

PORTD |= 1
установит "1" (сигнал высокого уровня) на выводе PD3.

PORTD &= ~(1
установит "0" (сигнал низкого уровня) на выводе PD4.

В AVR GCC сдвиг можно осуществлять и с помощью функции _BV() , которая выполняет поразрядный сдвиг и вставляет результат в компилируемый код.

В случае использования функции _BV() две предыдущие команды будут выглядеть следующим образом.

PORTD |= _BV(PD3); // установить "1" на линии 3 порта D

PORTD &= ~_BV(PD4); // установить "0" на линии 4 порта D

В зависимости от способа подключения светодиод будет загораться либо от сигнала высокого уровня, подаваемого на вывод PD1 микроконтроллера, как в первом случае, либо от сигнала низкого уровня в случае подключения, изображенного на втором рисунке.

/************************************************************************* ПРИМЕР ВКЛЮЧЕНИЯ СВЕТОДИОДА СИГНАЛОМ ВЫСОКОГО УРОВНЯ Пример подключения на рисунке 1 **************************************************************************/ #include $WinAVR = ($_GET["avr"]); if($WinAVR) include($WinAVR);?> int main(void ) { // начало основной программы DDRD = 0xff; PORTD |= _BV(PD1); // установить "1" (высокий уровень) на выводе PD1 }

Теперь попробуем мигнуть светодиодом, подключенным так, как это изображено на левом рисунке. Для этого используем функцию задержки _delay_ms().

Функция _delay_ms() формирует задержку в зависимости от передаваемого ей аргумента, выраженного в миллисекундах (в одной секунде 1000 миллисекунд). Максимальная задержка может достигать 262.14 миллисекунд. Если пользователь передаст функции значение более 262.14, то произойдет автоматическое уменьшение разрешения до 1/10 миллисекунды, что обеспечивает задержки до 6.5535 секунд. (О формировании более длительных задержек можно прочитать в статье .)

Функция _delay_ms() содержится в файле delay.h, поэтому нам будет необходимо подключить этот файл к программе. Кроме того, для нормальной работы этой функции необходимо указать частоту, на которой работает микроконтроллер, в герцах.

/************************************* ПРИМЕР МИГАНИЯ СВЕТОДИОДОМ Пример подключения на рисунке 1 **************************************/ #define F_CPU 1000000UL #include #include int main(void ) { // начало основной программы DDRD = 0xff; // все выводы порта D сконфигурировать как выходы PORTD |= _BV(PD1); _delay_ms(500); // ждем 0.5 сек. PORTD |= _BV(PD1); // установить "1" (высокий уровень) на выводе PD1, //зажечь светодиод _delay_ms(500); // ждем 0.5 сек. PORTD &= ~_BV(PD1); // установить "0" (низкий уровень) на выводе PD1, //погасить светодиод } // закрывающая скобка основной программы

Серия миганий светодиодом будет очень короткой. Для того чтобы сделать мигание непрерывным, можно организовать бесконечный цикл с помощью оператора безусловного перехода "goto". Оператор goto осуществляет переход к месту программы, обозначенному меткой. Имя метки не должно содержать пробелов. После имени метки ставится знак двоеточия. Между именем метки и двоеточием не должно быть пробелов.
/******************************************************* ПРИМЕР БЕСКОНЕЧНОГО МИГАНИЯ СВЕТОДИОДОМ Пример подключения на рисунке 1 ********************************************************/ #define F_CPU 1000000UL // указываем частоту в герцах #include #include int main(void ) { // начало основной программы DDRD = 0xff; // все выводы порта D сконфигурировать как выходы start: // метка для команды goto start PORTD |= _BV(PD1); // установить "1" (высокий уровень) на выводе PD1, //зажечь светодиод _delay_ms(250); // ждем 0.25 сек. PORTD &= ~_BV(PD1); // установить "0" (низкий уровень) на выводе PD1, //погасить светодиод _delay_ms(250); // ждем 0.25 сек. goto start; // перейти к метке start } // закрывающая скобка основной программы

Устройство микроконтроллера:
– назначение, устройство и программирование портов ввода-вывода микроконтроллера

Доброго дня уважаемые радиолюбители!
Приветствую вас на сайте “ “

Ну вот, уважаемые радиолюбители, сегодня этой статьей я закончу загрузку ваших (и своих) мозгов чистой теорией. Дальше будет легче и приятней: теорию совместим с практикой.
Ну а сегодня мы рассмотрим очень важный и интересный вопрос – порты ввода/вывода микроконтроллера .

Порты ввода/вывода микроконтроллера AVR

Порты ввода/вывода (далее я буду писать сокращенно – ПВВ ) – предназначены для общения микроконтроллера с внешними устройствами . С их помощью мы передаем информацию другим устройствам и принимаем информацию от них. В зависимости от типа, микроконтроллер может иметь на своем борту от одного до семи ПВВ . Каждому порту ввода/вывода присвоено буквенное обозначение – A, B, C, D, E, F, G. Все порты в микроконтроллере равнозначные, восьмиразрядные (содержат восемь линий, они же выводы, они же разряды, они же биты) и двунаправленные – могут как передавать, так и принимать информацию. ПВВ в микроконтроллере обслуживают все его устройства, в том числе и периферийные. Поэтому, в зависимости от того какое устройство будет работать с портом он может принимать и передавать или цифровую информацию, или аналоговую.

Вообще, порты классифицируются по типу сигнала :
цифровые порты – которые работают с цифровыми сигналами – логическими “нулями” и логическими “единицами”
- аналоговые порты – которые работают с аналоговыми сигналами – использующими плавно весь диапазон входных напряжений от нуля вольт до напряжения питания МК
- смешанные порты – они и используются в наших МК , могут оперативно переключаться с режима “цифровой порт” в режим “аналоговый порт”, и обратно.

В технической литературе и схемам ПВВ обозначаются следующим образом:
– “Р ” – первая буква, означающая слово “порт”
– “А ” (В, С, D, E, F, G) – вторая буква, обозначающая конкретный порт
– “0 ” (1, 2, 3, 4, 5, 6, 7) – третий символ – цифра, обозначающая конкретный вывод (регистр, бит) порта.
К примеру: “порт А” – РА , “пятый разряд порта А” – РА5 .
Если в МК есть несколько портов, то не обязательно их имена могут идти по порядку – A, B, C. Может быть и так – В, С, D. Поэтому пугаться и судорожно искать где же порт А не надо.
Кроме того, хотя порты восьмиразрядные, выводов у порта не обязательно должно быть 8, может быть и меньше, к примеру 3 – PA0, PA1, PA2. В таком случае порт называют неполным, или урезанным.
Давайте посмотрим на конкретный МК – ATmega8:

Как видите, в этом МК порта с именем “А” нет (отсутствует как класс;). Порт РВ и порт PD – полные, имеют по восемь выводов. А порт С – неполный (ущемленный, нет места в корпусе МК для его вывода), в нем отсутствует восьмой разряд (реально, внутри корпуса МК, он есть, но работать мы с ним не можем).

Для управления портами в их электрической схеме имеется два переключателя, которыми мы можем “щелкать” программно, используя специальные регистры ввода/вывода . Такие переключатели имеются для каждого вывода, что означает возможность управлять любым выводом порта. К примеру, один вывод порта можно настроить на ввод информации, три разряда этого же порта на вывод, а оставшиеся вообще не настраивать, оставить их в “Z- состоянии” .
Давайте разберемся с этим вопросом конкретней, на примере вот этой схемы:

Обратите внимание на два переключателя – Sin и Sout , и сопротивление Rup .
С помощью Sin осуществляется переключение вывода порта или для работы на вход, или для работы на выход. Управляется этот переключатель с помощью регистра ввода/вывода DDRx . У каждого порта свой регистр. Каждый разряд регистра управляет соответствующим разрядом порта (нулевой – нулевым, первый – первым и т.д.). Символ “x” в названии порта заменяется соответствующим именем порта: для порта А – DDRA, для порта С – DDRC. При записи в разряд регистра DDRx “единицы”, соответствующий ему разряд порта переключается на вывод информации, а при записи “нуля” – на ввод информации. Просмотрите рисунки ниже, и вы поймете как работать с регистром DDRx.

1. Переключение всех выводов порта на вывод информации:


2. Переключение всех выводов порта на ввод информации:


3. Переключение части выводов порта на ввод, и части на вывод информации:

В “классическом” Ассемблере настройка выводов портов на ввод и вывод информации выглядит так (просто пример 3-го рисунка):

Idi R20, 0b01100010 - этой командой мы записываем в РОН R20 двоичное число 01100010, которым определяем – какой вывод порта будет работать на вывод (1), а какой на ввод (0) информации. В данном случаем разряды порта В 1,5,6 – настраиваются на вывод информации, а 0,2,3,4,7 – на ввод информации
Out DDRB, R20 - этой командой мы переносим содержимое РОН R20 в регистр ввода/вывода порта В.

В Algorithm Builder запись немного отличается:
#b01100010 –> DDRB
Дело в том, что Algorithm Builder несколько более смещен к языкам высокого уровня, поэтому мы просто прописываем “свое желание” одной строчкой, но а при компилировании (переводе в машинные коды), программа сама преобразует эту строчку как и в “классической” записи.

Второй переключатель – Sout . Этот переключатель имеет двойное назначение, в зависимости от настройки разрядов порта на вывод или ввод информации.
Если разряд порта настроен на вывод информации , то с его помощью мы устанавливаем на выходе разряда или логическую “1”, или логический “0” .
Если разряд порта настроен на ввод информации
, то с его помощью подключается так называемый “подтягивающий резистор” – Rup , или “внутренний нагрузочный резистор”
. Благодаря этому резистору упрощается подключение внешних кнопок и переключателей, т.к. обычно контакты требуют внешнего резистора.
Как и переключатель Sin, Sout – это регистр ввода/вывода под названием PORTx , где “х” – буквенное обозначение порта (к примеру для порта D регистр будет иметь вид – PORTD).
В семейств МК Mega имеется дополнительный переключатель – PUD , - 2-й разряд регистра ввода/вывода SFIOR (он называется “Регистр специальных функций”). С помощью этого PUD осуществляется общее управление подтягивающими резисторами:
- при записи в этот разряд “1” – все подтягивающие резисторы для всех портов отключаются;
– при записи в этот разряд “0” – состояние подтягивающих резисторов определяется регистром PORTx.
Зачем нужно общее отключение резисторов, да и этот PUD заодно, мы сегодня рассматривать не будем.
В режиме работы разрядов порта на вывод, задача регистра PORTx очень проста – то, что мы в него запишем, то и будет на выходе. Запишем одни “нули” – на выходах буду логические нули, запишем “единицы” – на выходе буду логические “единицы”.
Например:

Idi R20, 0b11111111
Out DDRB, R20
Выводим в разряды 0-3 логический ноль, а в разряды 4-7 логическую единицу:
Idi R20, 0b11110000
Out PORTB, R20
В Algorithm Builder:
#b11111111 –> DDRB
#b11110000 –> PORTB
Надеюсь, что пока все понятно.
Вышеприведенные примеры позволяют настроить весь порт сразу, и вывести нужные значения на все выводы порта за один раз.
Если необходимо настроить только один разряд порта на ввод или вывод, а также вывести “0” или “1” только в один разряд порта, не затрагивая состояние и содержание других разрядов этого порта, существуют следующие команды:
SBI A,b – установить разряд регистра
CBI A,b – сбросить разряд регистра
При этом: “А ” – номер регистра , “b ” – разряд этого регистра.
Данные команды работают не только с РВВ DDRx и PORTx, но и с теми, которые имеют номера от 0 до 31.


Пример:
- “классический” Ассемблер:
Настраиваем порт В на вывод информации:
Idi R20, 0b11111111
Out DDRB, R20
Нам нужно переключить 1-й разряд порта на ввод информации:
CBI $17, 1 (где $17 – номер РВВ порта В – DDRB, 1 – разряд порта В)
- Algorithm Builder:
#b11111111 –> DDRB
0 –> PORTB.1

У портов ввода/вывода есть еще один регистр: PINx , регистр выводов порта (“х” – буквенное обозначение порта)
Этот регистр предназначен для считывания информации с вывода порта, независимо в какой он конфигурации – на ввод, или на вывод. Записать в этот регистр мы ничего не можем, он предназначен только для считывания.

Состояние выводов портов в зависимости от их конфигурации:

* PUD нет в МК Tiny и в МК модели ATMega161

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

При сбросе или включении питания микроконтроллера все выводы всех портов (за очень-очень редким случаем) переводятся в высокоимпедансное состояние – “Z- состояние”. Этот момент следует учитывать в реальных схемах. Если нагрузкой выхода служит транзисторный ключ, то для того, чтобы его база (затвор полевого транзистора) не болтались в воздухе, необходимо ставить дополнительные внешние резисторы сопротивлением 10-100 кОм.

Если вы не используете выводы порта, то не следует их оставлять “парящими в воздухе” – из-за этого повышается потребляемый ток МК (почему – не так важно, но это так). Все неиспользуемые выходы в схеме рекомендуется нагружать на сопротивления 10-100 кОм (можно использовать и внутренние подтягивающие резисторы), или переводить выводы в режим цифровых выходов.

При использовании аналогового компаратора, следует следить, чтобы подтягивающие резисторы были отключены – иначе пострадают показания абсолютных уровней сигнала .

Подтягивающие резисторы не совсем “резисторы” – их роль выполняют полевые транзисторы, которые имеют большой технологический разброс – номинал подтягивающего сопротивления может колебаться в пределах 30-100 кОм. При мощных помехах, да и в других “критических случаях” рекомендуется (хотя такой рекомендации и нет в даташитах) подключать дополнительные подтягивающие резисторы номиналом 2-5 кОм. Такие резисторы следует устанавливать на вывод “Reset”, на выводы внешних прерываний, если они не используются. Также следует устанавливать резисторы при работе выводов МК на общую шину (I2C, или просто при подсоединении выхода МК к выходу другого устройства с открытым коллектором, при подключении к двухвыводным кнопкам). Сопротивление встроенного резистора в таких случаях слишком велико, чтобы отсеивать электромагнитные помехи.

Создаем программу «мигалку»

Итак, уважаемые читатели, мы уже ознакомились со структурой микроконтроллера, разобрали простые команды ассемблера. Теперь можно приступить к написанию простой программы.

Для этого нам понадобится среда AVRStudio (о которой упоминалось раньше) и середа для симуляции микроконтроллера – Proteus 7. В сети маса примеров по установке этих программ, так что на этом останавливаться не будем.

Первая наша программа будет состоять из:

Подключения файла директив, инициализации МК;

Настройки портов ввода-вывода МК;

Простейшего цикла переключения портов из логического состояния «0» в «1»;

Подпрограммы простой задержки с использованием регистров общего назначения.

При штатной установке программы AVR Studio, файлы с директивами микроконтроллера располагается по следующему адресу C:\Program Files\Atmel\AVR Tools\AvrAssembler\Appnotes.

В нашем примере будем использовать микроконтроллер Attiny2313. Его inc файл имеет название 2313def.

Для начала откроем программу AVR Studio 4 и создадим проект.

Нажимаем на клавишу создания нового проекта.

В последнем окне необходимо выбрать симулятор и тип нашего МК. Далее, нажимаем на клавишу «finish» и можно будет увидеть, как откроется новое окно нашего проекта.

Наш проект уже создан и его можно наполнять программным кодом. Как говорилось раньше, первым делом нужно подключить файл директив данного микроконтроллера. Если возникнет необходимость проводить симуляцию проекта в среде AVR Studio 4, то желательно указать еще и имя нашего МК. Для этого нужно прописать следующую строку «.device ATtiny2313».

Для подключения inc файла, нужно прописать.include “tn2313def.inc”. Тем самым мы разрешим компилятору использовать файл директив данного МК.

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

К примеру, на следующем рисунке обозначена строка значения ОЗУ нашего МК. В программе мы пишем «spl», хотя можно написать и« $3d».

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

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

Итак, на Листинге 1 приведу пример нашей простой программы.

Листинг 1.

Device Attiny2313 ; указываем тип устройства

Include “tn2313def.inc” ; подключаем файл директив МК ATtiny2313

Def temp = r16 ; задаем имя нашему регистру общего назначения

Org 0x0000 ; начало программы с 0 адреса

ldi temp,ramend ; грузим значение ramend в регистр temp

ser temp ; настраиваем все выводы порта В на выход

out DDRB, temp ;

sbi portb,5; устанавливаем логическую «1» в PORTB5

Итак, разберем все по строкам, что мы сделали.

Первым делом, на всякий случай указали тип устройства.device Attiny2313.

Подключили файл директив.include “tn2313def.inc”.

Для простоты написания программы задали регистру R16 имя.def temp = r16. Такая операция хорошо будет упрощать написание программы в дальнейшем. Ведь словесное название регистра нам проще запомнить, нежели просто писать R16. Таким образом, можно присвоить имя любому регистру начинаю от R0 и заканчивая R31.

Командой ser temp мы грузим в регистр temp значение 255 и выгружаем его в out DDRB. Тем самым конфигурируем порт на выход. В дальнейшем, при симуляции программы в Proteus 7, мы увидем как данные порты приймут состояние логического нуля.

Устанавливаем на порте вывода PB5 логическую единицу с помощью команды sbi portb,5.

В самом конце необходимо организовать какой-то цикл, чтобы микроконтроллер не завис.

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

Открываем среду моделирования Proteus 7 и смотрим результат.

Теперь немного усложним задачу и заставим порт вывода переключаться с логического нуля в единицу. Для этого нам необходимо немного доработать нашу программу, Листинг 2. Все изменения происходит только в цикле «main», так что весь код не будем повторять.

Смотрим результат моделирования в среде Proteus 7, пподключив к выводу PB5 осциллограф.

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

Чтобы понизить скорость переключения, нам необходимо воспользоваться простой задержкой. На Листинге 3 показан простой пример реализации задержки.

Листинг 3.

sbi portb,5; устанавливаем логическую “1” в PORTB5

rcall delay ;вызываем подпрограмму задержки

cbi portb,5; устанавливаем логический “0” в PORTB5

clr r20; очистить регистры

inc r20; добавить 1

cpi r20,200 ; сравниваем, R20 = 200 ?

brne d_1; если не равно, то переходим по метке d_1, иначе пропускаем

После выполнения данной программы скорость переключения порта снизилась до 100мс. Задавая значения сравнения в регистры R20 и R21 можно регулировать этот интервал. На следующем рисунке видим результат работы программы.

На этом закончим. В следующей части мы разберем примеры программы с подключением кнопок, напишем цикл бегущей строки.

Предыдущие статьи:

♦Арифметико-логическое устройство и организация памяти – память программ, память данных, энергонезависимая память

Введение

Все порты ввода-вывода (ПВВ) AVR-микроконтроллеров работают по принципу чтение-модификация-запись при использовании их в качестве портов универсального ввода-вывода. Это означает, что изменение направления ввода-вывода одной линии порта командами SBI и CBI будет происходит без ложных изменений направления ввода-вывода других линий порта. Данное распространяется также и на изменение логического уровня (если линия порта настроена на вывод) или на включение/отключение подтягивающих резисторов (если линия настроена на ввод). Каждый выходной буфер имеет симметричную характеристику управления с высоким втекающим и вытекающим выходными токами. Выходной драйвер обладает нагрузочной способностью, которая позволяет непосредственно управлять светодиодными индикаторами. Ко всем линиям портов может быть подключен индивидуальный выборочный подтягивающий к плюсу питания резистор, сопротивление которого не зависит от напряжения питания. На всех линиях ПВВ установлены защитные диоды, которые подключены к VCC и Общему (GND), как показано на рисунке 29. Подробный перечень параметров ПВВ приведен в разделе "Электрические характеристики".

Рисунок 29 – Эквивалентная схема линии ПВВ

Ссылки на регистры и биты регистров в данном разделе даны в общей форме. При этом, символ “x” заменяет наименование ПВВ, а символ “n” заменяет номер разряда ПВВ. Однако при составлении программы необходимо использовать точную форму записи. Например, PORTB3, означающий разряд 3 порта B, в данном документе записывается как PORTxn. Адреса физических регистров ввода-вывода и распределение их разрядов приведены в разделе “Описание регистров портов ввода-вывода".

Для каждого порта ввода-вывода в памяти ввода-вывода зарезервировано три ячейки: одна под регистр данных – PORTx, другая под регистр направления данных – DDRx и третья под состояние входов порта – PINx. Ячейка, хранящая состояние на входах портов, доступна только для чтения, а регистры данных и направления данных имеют двунаправленный доступ. Кроме того, установка бита выключения подтягивающих резисторов PUD регистра SFIOR отключает функцию подтягивания на всех выводах всех портов.

Ниже приведено описание порта ввода-вывода для универсального цифрового ввода-вывода. Большинство выводов портов поддерживают альтернативные функции встроенных периферийных устройств микроконтроллера. Описание альтернативных функций приведено далее в подразделе “Альтернативные функции порта” (см. также описание функций соответствующих периферийных модулей).

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

Порты в качестве универсального цифрового ввода-вывода

Все порты являются двунаправленными портами ввода-вывода с опциональными подтягивающими резисторами. Рисунок 30 иллюстрирует функциональную схему одной линии порта ввода-вывода, обозначенный как Pxn.


Рисунок 30 – Организация универсального цифрового ввода-вывода (1)

Прим. 1: Сигналы WPx, WDx, RRx, RPx и RDx являются общими в пределах одного порта. Сигналы clkI/O, SLEEP, и PUD являются общими для всех портов.

Настройка выводов

Режим и состояние для каждого вывода определяется значением соответствующих разрядов трех регистров: DDxn, PORTxn и PINxn. Как показано в “Описании регистров портов ввода-вывода” доступ к битам DDxn возможен по адресу DDRx в пространстве ввода-вывода и, соответственно, к битам PORTxn по адресу PORTx, а к битам PINxn по адресу PINx.

Биты DDxn регистра DDRx определяют направленность линии ввода-вывода. Если DDxn = 1, то Pxn конфигурируется на вывод. Если DDxn=0, то Pxn конфигурируется на ввод.

Если PORTxn = 1 при конфигурации линии порта на ввод, то разрешается подключение подтягивающего резистора. Для выключения данного резистора необходимо записать в PORTxn лог. 0 или настроить линию порта на вывод. Во время сброса все линии портов находятся в третьем (высокоимпедансном) состоянии, даже если не работает синхронизация.

Если PORTxn = 1 при конфигурации линии порта на вывод, то состояние выхода будет определяться значением PORTxn.

Поскольку одновременная запись в регистры DDRx и PORTx невозможна, то при переключении между третьим состоянием ({DDxn, PORTxn} = 0b00) и выводом лог. 1 ({DDxn, PORTxn} = 0b11) должно возникнуть промежуточное состояние или с подключенным подтягивающим резистором ({DDxn, PORTxn} = 0b01) или с выводом лог. 0 ({DDxn, PORTxn} = 0b10). Как правило, переход через состояние с подключением подтягивающего резистора эквивалентно состоянию вывода лог.1, если вывод микроконтроллера связан с высокоимпедансным входом. В противном случае, необходимо установить бит PUD регистра SFIOR для выключения всех подтягивающих резисторов на всех портах

Переключение между вводом с подтягивающими резисторами и выводом низкого уровня связано с аналогичной проблемой. Поэтому, пользователь вынужден использовать или третье состояние ({DDxn, PORTxn} = 0b00) или вывод лог. 1 ({DDxn, PORTxn} = 0b11) в качестве промежуточного шага.

В таблице 25 подытоживается действие управляющих сигналов на состояние вывода.

Таблица 25 – Настройка вывода порта

DDxn PORTxn PUD (в SFIOR) Ввод-вывод Подтягивающий резистор Комментарий
0 0 X Ввод Нет
0 1 0 Ввод Да Pxn будет источником тока при подаче внешнего низкого уровня
0 1 1 Ввод Нет Третье состояние (Z-состояние)
1 0 X Вывод Нет Вывод лог. 0 (втекающий ток)
1 1 X Вывод Нет Вывод лог. 1 (вытекающий ток)

Считывание состояние вывода

Независимо от значения бита направления данных DDxn состояние вывода порта может быть опрошено через регистровый бит PINxn. Как показано на рисунке 30 регистровый бит PINxn и предшествующая ему триггерная защелка составляют синхронизатор. Данный подход позволяет избежать метастабильности, если изменение состояния на выводе произошло около фронта внутренней синхронизации. Однако такой подход связан с возникновением задержки. На рисунке 31 представлена временная диаграмма синхронизации во время опроса внешне приложенного к выводу уровня. Длительности минимальной и максимальной задержек на распространение сигнала обозначены как tpd,max и tpd,min, соответственно.


Рисунок 31 – Синхронизация во время опроса приложенного к выводу порта уровня

В следующих примерах показано как установить на линиях 0 и 1 порта В уровень лог. 1, а на линиях 2 и 3 – лог. 0, а также как настроить линии 4…7 на ввод с подключением подтягивающих резисторов на линиях 6 и 7. Результирующее состояние линий считываются обратно, но, с учетом сказанного выше, включена инструкция nop для обеспечения возможности обратного считывания только что назначенного состояния некоторых выводов.

Пример кода на Ассемблере (1) ... ; Разрешаем подтягивание и устанавливаем высокие выходные уровни; Определяем направления данных линий портов ldi r16,(1<

Прим. 1: В программе на Ассемблере используются два временных регистра для минимизации интервала времени от настройки подтягивающих резисторов на разрядах 0, 1, 6 и 7 до корректной установки бит направления, разрешающих вывод лог. 0 на линиях 2 и 3 и заменяющих высокий уровень на разрядах 0 и 1, образованный за счет подключения подтягивающих резисторов, на высокий уровень сильноточного драйвера.

Разрешение цифрового ввода и режимы сна

Как показано на рисунке 30 входной цифровой сигнал может быть зашунтирован к общему на входе триггера Шмита. Сигнал, обозначенный на рисунке как SLEEP, устанавливается при переводе микроконтроллера в режим выключения (Power-down), экономичный режим, дежурный режим и расширенный дежурный режим. Это позволяет избежать повышения потребляемого тока в случае, если некоторые входные сигналы окажутся в плавающем состоянии или уровень входного аналогового сигнала будет близок к VCC/2.

Сигнал SLEEP игнорируется по входам внешних прерываний. Если запросы на внешнее прерывание отключены, то SLEEP действует и на эти выводы. SLEEP также игнорируется на некоторых других входах при выполнении их альтернативных функций (см. “Альтернативные функции порта ”).

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

Неподключенные выводы

Если несколько выводов остаются неиспользованными, то рекомендуется гарантировать на них присутствие определенного логического уровня. Не смотря на то, что большинство цифровых входов отключены в режимах глубокого сна, как описано выше, необходимо избежать наличия плавающих входов во избежание повышенного потребления тока во всех других режимах работы микроконтроллера, где цифровой ввод разрешен (Сброс, Активный режим и режим холостого).

Самым простым методом гарантирования присутствия определенного уровня на неиспользуемом выводе является разрешение подключения внутреннего подтягивающего резистора. Однако в этом случае в режиме сброса подтягивающие резисторы будут отключены. Если требуется малое потребление и в режиме сброса, то необходимо устанавливать внешний подтягивающий резистор к плюсу или к минусу питания. Подключение выводов непосредственно к VCC или GND не рекомендуется, т.к. может возникнуть опасный ток при случайной конфигурации такого вывода на вывод данных.

В этом статье мы напишем первую программу и научимся программировать порты ввода-вывода микроконтроллера.

Наша первая программа будет управлять, по началу, одним из выводов микроконтроллера. Для того чтобы удостоверится в том, что программа работает, к управляемому выводу через токоограничивающий резистор мы подключим светодиод, анод которого соединен с выводом МК, а катод с минусом (общим проводом).

По умолчанию, на всех выводах незапрограммированного микроконтроллера напряжение близкое к нулю, поэтому светодиод не будет светиться. Наша задача состоит в том, чтобы написать программу, с помощью которой на выводе МК появится напряжение +5 В. Это напряжения (точнее буде ток) засветит светодиод.

Порты ввода-вывода микроконтроллера

Микроконтроллер ATmega8 имеет 28 выводов, каждый из них выполняет определенные функции. Светодиод можно подключить к большинству выводов, однако, не ко всем, ведь минимум пара выводов занята под питание. Чтобы четко знать назначение каждого вывода МК воспользуется даташитом. В даташите находим распиновку (обозначение) всех выводов.

Почти каждый вывод может выполнять несколько функций. Сокращения названий функций приводятся в скобках рядом с выводами. В последующих статья мы обязательно их все рассмотрим. Сейчас же нас интересуют некоторые из них.

Для работы микроконтроллера, впрочем, как и любой другой микросхемы, необходимо напряжение. Во избежание ложных срабатываний МК нужно питать только стабильным напряжением от 4,5 В до 5,5 В. Этот диапазон напряжений строго регламентирован и приведен в даташите.

Плюс («+») источника питания подсоединяется в 7-й ножке, обозначенной VCC. Минус («-») – к 8-й или 22-й ножке, которые имеют обозначение GND (GND – сокращенно от ground – «земля»).

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

Микроконтроллер ATmega8 имеет три порта ввода-вывода: B, C и D. Порты могут быть полными и неполными. Полный порт состоит из восьми бит и соответственно имеет столько же одноименных выводов. У неполного порта меньше 8 бит, поэтому число выводов такого порта также менее восьми.

У данного МК порты B и D полные. А порт C неполный и имеет семь бит. Еще раз обращаю внимание, что нумерация битов начинается с нуля, например PB0, PB1, PB2…

Не удивляйтесь, что у ATmega8 отсутствует порт A. Другие МК, имеющие большее число выводов, могут содержать как порт A, так и порт E. У микроконтроллеров с меньшим числом выводов может быть только один порт и тот неполный.

Знакомство с Atmel Studio 7

Теперь перейдем к написанию кода программы. Наша первая программа будет устанавливать + 5 В на нулевом бите порта C PC0, т.е. на 23-м выводе микроконтроллера.

Запускаем Atmel Studio.

Сначала необходимо создать проект. Для этого в открывшемся окне кликаем по вкладке New Project .

Также проект можно создать, кликнув по вкладке File . В выпавшем меню следует выбрать New и далее Project . Или нажать комбинацию клавиш Ctrl+Shift+N .

В появившемся окне выбираем язык программирования C/C++ и кликаем по вкладке.


Далее нам нужно задать имя и место на диске для нашего проекта. Назовём наш первый проект именем 1 в строке для ввода Name . Изменить место расположения файла можно кликнув по кнопке Browse напротив строки Location . Если оставить галочку возле Create directory for solution , то в выбранном месте автоматически создастся папка с именем проекта. В данной папке помимо проекта будут созданы и другие вспомогательные файлы, поэтому я рекомендую не убирать галочку.

После того, как имя проекта и его место выбраны, нажимаем кнопку OK . Снова появляется окно. В нем нам нужно выбрать тип микроконтроллера. В нашем случае – это ATmega8 . Кликаем по вкладке Device Family . В выпавшем меню выбираем серию микроконтроллеров ATmega .

С помощью прокрутки находим и выделяем микроконтроллер ATmega8 и наживаем кнопку OK .

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

Рассмотрим все по порядку.

Atmel Studio 7 | Первая программа

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

Цвет комментариев и других элементов программы можно изменять в настройках Atmel Studio.

* 1.c

* Created: 07.08.2017 16:57:59

Комментарии бываю однострочные и многострочные. В данном шаблоне программы применяются многострочные комментарии. Они начинаются косой линией со звездочкой, а заканчиваются звездочкой с косой линией.

/* — начало комментария

*/ — конец комментария

Весь текс, который помещен между /* и */ полностью пропускается компилятором.

Однострочный комментарий обозначается двумя косыми линиями и действует в пределах одной строки. Текст перед двумя косыми распознается компилятором как код программы, а после – как комментарий.

// Здесь пишется однострочный комментарий

На практике использование комментариев считается хорошим тоном программирования. В дальнейшем мы будем применять оба их типа.

Директива препроцессора

Следующим элементом программы является строка

#include

Эта строка указывает компилятору, что к данному файлу нужно подключить другой файл с именем io.h , который находится в папке avr. В подключаемом файле находится информация о базовых настройках микроконтроллера.

По сути, можно было бы и не подключать файл io.h , а набрать его содержимое вручную, однако это очень неудобно.

Знак # означает, что данная команда – это директива препроцессора. Дело в том, что прежде чем скомпилировать файл компилятору необходимо выполнит предварительную его обработку. Поэтому сначала выполняется некая подготовка файла к компиляции путем добавления некоторых инструкций, прописанных в файле io.h

io – название файла, которое происходит от сокращения input/output – ввод/вывод.

H – расширение файла, название его происходит от слова header – заголовок.

Теперь все вместе. io.h – это заголовочный файл, в котором записана информация о настройках ввода-вывода микроконтроллера.

Главная функция main

Ниже нам встречается следующая строка:

int main (void )

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

В синтаксисе языка Си идентификатором функции служат круглые скобки

Внутри скобок помещено слово void (void). Оно обозначает пустота. Это указывает на то, что функция main ничего не принимает, т. е. не принимает никаких аргументов. По мере написания более сложных программ, мы детальнее остановимся на этом моменте.

int – это целочисленный тип данных. В данном случае функция работает с целыми числами: как положительными, так и отрицательными. Существуют и другие типы данных, например с плавающей запятой, символьные и др. Более подробно мы будем рассматривать типы данных по мере необходимости или в отдельной статье. Однако для функции main рекомендуется всегда использовать тип данных int, поскольку конструкция int main (void ) определена стандартом языка Си и распознается любым компилятором.

Область действия функции определяется фигурными скобками

. → тело функции

Код программы, помещенный между открывающейся и закрывающейся скобками, относится к телу функции.

В общем случае любая функция имеет следующую конструкцию:

тип данных имя функции (агрумент )

тело функции (код)

Функция while

Внутри функции main находится функция while :

while (1)

While переводится с английского «пока». Это говорит о том, что программа, которая находится в теле данной функции, будет выполняться до тех пор, пока условие истинно. Единица в круглых скобках указывает, что условие истинно, поэтому код программы, написанный в данной функции, будет повторяться бесконечное число раз, т.е. программа будет зациклена. Для чего это делается? Дело в том, что микроконтроллер должен непрерывно выполнять записанную программу. Поэтому программа не может просто взять и оборваться. Микроконтроллер всегда опрашивает порты ввода-вывода либо выдает в них сигналы, даже находясь в ждущем режиме.

Теперь, когда мы рассмотрели основные элементы конструкции программы, давайте посмотрим целиком на базовый шаблон. Без комментариев он имеет следующий вид:

#include

int main (void )

while (1)

Программирование портов ввода-вывода микроконтроллера ATmega8

Сейчас мы уже можем дополнить программу нужным нам кодом. Первым делом необходимо настроить нулевой бит порта C PC0 на выход.

Мы уже знаем, что МК может, как принимать, так и выдавать сигнал, т.е. выводы (порты) его могут работать как входы и как выходы . Поэтому предварительно нужно настроить вывод МК на соответствующий режим. Для этого в микроконтроллере есть специальный регистр, который называется DDR – d irect d ata r egister – регистр направления данных.

У каждого порта есть свой такой регистр. Например, регистр порта C называется DDRC , порта B – DDRB , порта D – DDRD .

Чтобы настроить вывод порта на вход в регистр DDR необходимо записать ноль , а на выход единицу .

Команда настройки нулевого бита порта C выглядит следующим образом

DDRC = 0b0000001;

Данной командой в регистр DDRC записывается двоичное число равное десятичному 1. Префикс 0b идентифицирует данное число, как двоичное.

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

Также можно записать в регистр шестнадцатеричное число:

DDRC = 0x1;

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

Давайте рассмотрим еще один пример. Допустим нам необходимо настроить нулевой, третий и седьмой биты порта B на выход, а остальные биты на вход. Для этого случая код имеет такой вид:

DDRB = 0b10001001;

Регистр микроконтроллера PORT

После того, как мы настроили нулевой бит порта C PC0 на выход, нужно еще выполнить настройку, чтобы на данном выводе появилось напряжение +5 В. Для этого необходимо установить нулевой бит в регистре PORT . Если бит установлен в единицу , то на выводе будет +5 В (точнее говоря величина напряжения питания микроконтроллера, которая может находится в пределах 4,5…5,5 В для микроконтроллера ATmega8). Если бит установлен в ноль , — то на выводе будет напряжение, величина которого близка к нулю .

Каждый порт имеет свой регистр: порт A – PORTA , порт B – PORTB , порт C – PORTC .

И так, чтобы получить на выводе PC0 напряжение +5 В, необходимо записать такую команду:

PORT = 0b0000001;

Обратите внимание на то, что каждая команда заканчивается точкой с запятой.

Таким образом, чтобы засветить светодиод, нам необходимы всего лишь две команды:

DDRC = 0b0000001;

PORTС = 0b0000001;

Первой командой мы определяем вывод PC0 как вход, а второй устанавливаем на нем напряжение +5 В.

Полный код программы выглядит так:

#include

int main (void )

DDRC = 0b0000001;

While (1)

PORTC = 0b0000001;

Здесь необходимо заметить следующее: команда DDRC = 0b0000001; выполнится всего один раз, а команда PORTC = 0b0000001; будет выполняться все время в цикле, поскольку она находится в теле функции while (1) . Но даже если мы вынесем команду за пределы функции и поместим ее после DDRC = 0b0000001; , светодиод и в этом случае будет светиться все время. Однако, разместив команду PORTC = 0b0000001 ; в теле while (1) , мы делаем явный акцент на том, что светодиод должен светится все время.

Компиляция файла

Теперь, когда код полностью готов, его нужно скомпилировать. Для этого необходимо нажать клавишу F7 или кликнуть по кнопке Build и в выпавшем меню выбрать Build Solution .

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

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

  • Tutorial

Работа портов ввода/вывода

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

Пример будем рассматривать на микроконтроллере ATMega8 .

Программу писать будем в Atmel Studio 6.0 .

Эмулировать схему будем в Proteus 7 Professional .

С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:

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

Каждый порт микроконтроллера AVR (обычно имеют имена A, B и иногда C или даже D) имеет 8 разрядов, каждый из которых привязан к определенной ножке корпуса. Каждый порт имеет три специальных регистра DDRx , PORTx и PINx (где x соответствует букве порта A, B, C или D). Назначение регистров:

DDRx – Настройка разрядов порта x на вход или выход.

PORTx – Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).

PINx –Чтение логических уровней разрядов порта x.

PINхn – это регистр чтения. Из него можно только читать. В регистре PINxn содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе - читаем соответствующий бит регистра PINxn . Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы - пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PINx переключится с 1 на 0 только при снижении напряжение ниже 1.4 вольт, а вот когда напряжение нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1.8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.

При снижении напряжения питания разумеется эти пороги также снижаются.

DDRxn – это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PINxn это значения не имеет. Читать из PINxn реальное значение можно всегда).

DDRxy = 0 – вывод работает как ВХОД.

DDRxy = 1 – вывод работает на ВЫХОД.

PORTxn – режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORTх зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).

Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxn =1 то на выводе лог.1, если PORTxn =0 то на выводе лог.0.

Когда ножка настроена на вход, то если PORTxn =0, то вывод в режиме Hi-Z. Если PORTxn =1 то вывод в режиме PullUpс подтяжкой резистором в 100к до питания.

Таблица. Конфигурация выводов портов.

DDRxn PORTxn I/O Comment
0 0 I (Input) Вход Высокоимпендансный вход. (Не рекомендую использовать, так как могут наводится наводки от питания)
0 1 I (Input) Вход Подтянуто внутренне сопротивление.
1 0 O (Output) Выход На выходе низкий уровень.
1 1 O (Output) Выход На выходе высокий уровень.

Общая картина работы порта показана на рисунках:

Рис. DDRxn =0 PORTxn =0 – Режим: HI-Z – высоко импендансный вход.

Рис. DDRxn =0 PORTxn =1 – Режим: PullUp – вход с подтяжкой до лог.1.

Рис. DDRxn =1 PORTxn =0 – Режим: Выход – на выходе лог.0. (почти GND)

Рис. DDRxn =1 PORTxn =1 – Режим: Выход – на выходе лог.1. (почти VCC)

Вход Hi-Z - режим высокоимпендансного входа.
Этот режим включен по умолчанию. Все ключи разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PINn и мы всегда можем узнать что у нас на входе - единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц - наводка от сети 220В, а в регистре PINn будет меняться 0 и 1 с частотой около 50Гц

Вход PullUp - вход с подтяжкой.
При DDRxn =0 и PORTxn =1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит не подключенную никуда линию в состояние лог.1. Цель подтяжки очевидна - не допустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог.1 и на входе будет лог.0.

Режим выхода.
Тут, думаю, все понятно - если нам надо выдать в порт лог.1, мы включаем порт на выход (DDRxn =1) и выдаем лог.1 (PORTxn =1) - при этом замыкается верхний ключ и на выводе появляется напряжение, близкое к питанию. А если надо лог.0, то включаем порт на выход (DDRxn =1) и выдаем лог.0 (PORTxn =1) - при этом открывается уже нижний вентиль, что дает на выводе около нуля вольт.