Перейти к содержанию

Регистры порта AVR: DDRx, PORTx, PINx

pinMode(), digitalWrite() и digitalRead() в Arduino сводятся к трём регистрам каждого порта ATmega328P. Один задаёт направление, второй — выходной уровень или подтяжку, третий читает вывод. Зная их, можно переключить восемь выводов одной инструкцией.

Обновлено Редакция GAW

Регистры порта AVR — три восьмибитных регистра, через которые программа управляет каждым портом ввода-вывода: DDRx (Data Direction Register), PORTx и PINx, где x — буква порта (B, C, D у ATmega328P). Каждому выводу Pxn соответствует по одному биту в каждом из них: DDxn, PORTxn и PINxn. Это AVR-реализация вывода общего назначения.

Назначение битов

DDxnPORTxnPUD (MCUCR)Режим вывода
00любойвход, третье состояние (Hi-Z)
010вход с подтягивающим резистором
011вход, третье состояние
10любойвыход, низкий уровень (втекающий ток)
11любойвыход, высокий уровень (вытекающий ток)
  • DDRx задаёт направление: единица — выход, ноль — вход. После сброса все выводы — входы в третьем состоянии.
  • PORTx на выходе задаёт уровень, на входе включает подтяжку.
  • PINx читает реальное состояние вывода при любом направлении — через синхронизатор, который вносит задержку. Запись единицы в бит PINx, по даташиту, инвертирует соответствующий бит PORTx — так светодиод переключают одной инструкцией.

Тонкости

Переход из третьего состояния (00) в высокий выход (11) невозможен за одну запись в два регистра: в промежутке вывод окажется либо входом с подтяжкой (01), либо низким выходом (10). Microchip отмечает, что подтяжка обычно безопасна, а если нет — подтяжки можно отключить битом PUD. Аналогично переход между входом с подтяжкой и низким выходом делают через третье состояние или высокий выход.

Запись сразу в весь порт меняет восемь выводов одновременно: PORTB = 0x20 включает только PB5. Чтобы изменить один бит, используют маску: PORTB |= (1 << PB5) — в общем случае это чтение-модификация-запись, и если тот же порт меняет обработчик прерывания, операцию нужно делать атомарной. Но PORTB лежит в младших регистрах ввода-вывода (адрес 0x05), а инструкции SBI и CBI работают как раз с адресами 0–31: для установки или сброса одного бита константной маской оптимизатор, как сказано в FAQ AVR-LibC, превращает такую запись в одну инструкцию SBI или CBI, а одна инструкция не может быть прервана посередине. С маской из нескольких бит или с регистрами выше 0x1F получается чтение, изменение и запись в несколько инструкций.

Связь с Arduino

pinMode(), digitalWrite() и digitalRead() в ядре ArduinoCore-avr по номеру вывода находят порт и маску бита и работают именно с этими регистрами, добавляя проверки и отключение ШИМ. Прямой доступ к регистрам заметно быстрее — разбор с измерением тактов в уроках «pinMode и digitalWrite изнутри» и «digitalRead и INPUT_PULLUP изнутри», а первая программа на регистрах — в уроке «Порты и регистры: мигаем светодиодом».

Частые вопросы

Как включить внутреннюю подтяжку на входе AVR?

Сделать вывод входом (DDxn = 0) и записать единицу в PORTxn. По табл. 14-1 даташита ATmega328P при PUD = 0 это даёт вход с подтяжкой: вывод отдаёт ток, если его снаружи замкнуть на землю. Бит PUD в регистре MCUCR отключает подтяжки на всех портах сразу. В Arduino это pinMode(pin, INPUT_PULLUP).

Как переключить вывод AVR одной инструкцией?

Записать единицу в соответствующий бит регистра PINx: по даташиту это инвертирует PORTxn независимо от DDRxn. Например, PINB = (1 << PB5) переключает светодиод на выводе 13 Arduino Uno. Для одного бита компилятор может использовать инструкцию SBI — Microchip прямо это допускает.

Почему сразу после записи в PORTx чтение PINx даёт старое значение?

Из-за синхронизатора на входе: бит PINxn и защёлка перед ним защищают от метастабильности, но вносят задержку. В примере даташита после out PORTx стоит nop, и только потом in PINx — иначе программно выставленный уровень ещё не успевает дойти до регистра входа.

Источники

  1. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet Complete (DS40002061B, 2020), разд. 14.2 «Ports as General Digital I/O» и 14.4 «Register Description» — 14.2.1: у каждого вывода три бита — DDxn (в DDRx), PORTxn (в PORTx), PINxn (в PINx); DDxn = 1 — выход, 0 — вход; PORTxn = 1 на входе включает подтяжку; при сбросе выводы в третьем состоянии; 14.2.2: запись единицы в PINxn переключает PORTxn независимо от DDRxn, для одного бита можно использовать SBI; 14.2.3: переход между третьим состоянием (00) и высоким выходом (11) идёт через подтяжку (01) или низкий выход (10), PUD в MCUCR отключает все подтяжки; табл. 14-1: 00 — вход Hi-Z, 01 при PUD = 0 — вход с подтяжкой, 01 при PUD = 1 — Hi-Z, 10 — выход низкий (sink), 11 — выход высокий (source); 14.2.4: PINxn читается при любом DDxn, бит и защёлка образуют синхронизатор против метастабильности, который вносит задержку; программно записанное значение читается через PINx только после nop
  2. Arduino. ArduinoCore-avr 1.8.8 (commit 86df345), cores/arduino/wiring_digital.c — pinMode, digitalWrite и digitalRead находят по номеру вывода порт и маску бита и работают с регистрами направления, выхода и входа
  3. Microchip. AVR Instruction Set Manual (DS40002198B, 2021), разд. 6.95 «SBI — Set Bit in I/O Register» — SBI устанавливает указанный бит в регистре ввода-вывода и работает только с младшими 32 регистрами — адреса 0–31; одна инструкция; в DS40002061B регистр PORTB имеет адрес 0x05 (0x25 в пространстве данных)
  4. AVR-LibC 2.3.2: FAQ, раздел «How do I use a #define'd constant in an asm statement?» — стандартные битовые операции C вида PORTB |= (1 << 7) оптимизатор превращает в одну команду SBI, если позволяют операнды

Нашли ошибку?