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

Порты и регистры: мигаем светодиодом

Каждый вывод ATmega328P управляется тремя битами в трёх регистрах порта: направление, выходное значение и входное значение. Научившись обращаться с ними, можно управлять любым выводом без digitalWrite.

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

AVR с нуляурок 2 из 825 мин

Цель урока: Настраивать выводы ATmega328P регистрами DDRx и PORTx, менять отдельные биты масками и мигать светодиодом с задержкой _delay_ms.

Микроконтроллер общается с внешним миром выводами. У ATmega328P они объединены в порты по восемь: B, C и D. На плате Arduino Uno цифровые выводы 0–7 — это порт D, выводы 8–13 — порт B, а аналоговые A0–A5 — порт C. Вывод 13 со светодиодом L — бит 5 порта B, или PB5.

Три регистра порта

Каждый вывод Pxn управляется тремя битами в трёх регистрах (раздел 14.2.1 даташита), где x — буква порта, n — номер бита:

РегистрБитЧто делает
DDRxDDxnнаправление: 1 — выход, 0 — вход
PORTxPORTxnу выхода — уровень (1 — высокий, 0 — низкий); у входа — 1 включает подтягивающий резистор
PINxPINxnчтение уровня на выводе; запись 1 переключает PORTxn

После сброса регистры DDRx и PORTx равны нулю (у PINx начального значения нет — он отражает уровни на выводах), и выводы находятся в третьем состоянии — это входы без подтяжки (разделы 14.2.1 и 14.4). Поэтому любой вывод перед использованием как выход надо явно настроить.

Маски битов

Чтобы изменить один бит и не тронуть остальные, используют побитовые операции и маску _BV(n) = 1 << n:

PORTB |= _BV(PORTB5);    /* set bit 5: PB5 high */
PORTB &= ~_BV(PORTB5);   /* clear bit 5: PB5 low */
PORTB ^= _BV(PORTB5);    /* invert bit 5 */
if (PINB & _BV(PINB5)) { /* bit 5 of the pin register is 1 */ }

У ATmega328P есть и аппаратный способ переключить вывод: запись единицы в бит регистра PINx инвертирует соответствующий бит PORTx (раздел 14.2.2). Строка PINB = _BV(PINB5); меняет только PB5 и делает это одной записью, без чтения порта. Подробнее о масках, сдвигах и приоритетах операций — в статье «Битовые операции в C».

Мигаем светодиодом

#define F_CPU 16000000UL  /* must be defined before util/delay.h */
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRB |= _BV(DDB5);            /* PB5 (Arduino pin 13) is an output */

    for (;;) {
        PINB = _BV(PINB5);        /* writing 1 to PINB5 toggles PORTB5 */
        _delay_ms(500);
    }
}

Если частота уже задана ключом -DF_CPU=16000000UL в команде сборки, строку #define можно убрать. Функция _delay_ms из avr-libc — это пустой цикл на нужное число тактов. Она работает правильно, только если включена оптимизация (-Os), а время задержки — константа, известная при компиляции. Почему частота так важна, разберём в уроке 4.

Внешний светодиод и ток вывода

Подключим второй светодиод к выводу 7 платы (PD7) через резистор 330 Ом к земле. При прямом падении на красном светодиоде около 2 В ток составит примерно (5 − 2) / 330 ≈ 9 мА — расчёт наш, без учёта падения внутри выходного каскада. Резистор под другой светодиод и питание посчитает калькулятор резистора для светодиода.

Предельные значения из даташита ATmega328P:

ОграничениеЗначениеРаздел
Постоянный ток одного вывода, абсолютный максимум40 мА29.1
Ток через выводы VCC и GND200 мА29.1
Сумма вытекающих токов выводов B0–B5, D5–D7, ADC6, XTAL1, XTAL2 (светодиод от вывода к земле)не больше 150 мАтабл. 30-1, прим. 3
Сумма втекающих токов выводов B0–B5, D5–D7, XTAL1, XTAL2 (светодиод от плюса к выводу)не больше 100 мАтабл. 30-1, прим. 4
Напряжение низкого уровня при 20 мА и 5 Вне больше 0,9 Втабл. 30-1

Абсолютный максимум — это не рабочий режим, а граница повреждения. Нормальная нагрузка на вывод — единицы миллиампер; для реле или двигателя нужен транзисторный ключ.

Задание

  1. Какое число надо записать в DDRB одной операцией DDRB |= …, чтобы сделать выходами PB0, PB1 и PB5 (выводы 8, 9 и 13 Uno)? Ответ дайте в шестнадцатеричном виде.
  2. Перепишите цикл мигания так, чтобы светодиоды на PB5 и PD7 горели по очереди.
Ответ и решение
  1. Маска _BV(0) | _BV(1) | _BV(5) = 0b00100011 = 0x23.

  2. Один из вариантов:

DDRB |= _BV(DDB5);
DDRD |= _BV(DDD7);
PORTB |= _BV(PORTB5);         /* start: PB5 on, PD7 off */

for (;;) {
    PINB = _BV(PINB5);        /* toggle both LEDs, */
    PIND = _BV(PIND7);        /* they stay in opposite states */
    _delay_ms(500);
}

Что дальше

В уроке 3 настроим вывод входом, включим подтяжку и прочитаем кнопку. Все регистры портов ATmega328P с адресами и битами — на странице регистров GPIO ATmega328P. Сколько тактов на самом деле занимает _delay_ms при разных частотах, покажет калькулятор задержек AVR. Что такое вывод общего назначения, коротко объясняет термин GPIO, а предельные токи входа и выхода — термин «Втекающий и вытекающий ток».

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

Чем отличаются регистры PORTB и PINB?

В PORTB программа пишет, что выдавать на выводы-выходы, а у входов этот же бит включает подтягивающий резистор. PINB только читают: в нём состояние самих выводов после синхронизатора, независимо от направления. Запись единицы в бит PINB у ATmega328P переключает соответствующий бит PORTB.

Зачем писать PORTB |= _BV(PORTB5), а не PORTB = 0x20?

Операция |= меняет только нужный бит и сохраняет остальные, а присваивание 0x20 обнулит все другие биты порта — например, погасит светодиоды на соседних выводах или отключит их подтяжки. Присваивание целого байта уместно только при начальной настройке, когда известно назначение всех восьми выводов.

Сколько светодиодов можно подключить к портам ATmega328P?

По току одного вывода абсолютный максимум 40 мА, но важнее суммарные ограничения даташита: светодиод, включённый от вывода к земле, питается вытекающим током, и сумма таких токов для группы выводов B0–B5, D5–D7, ADC6, XTAL1, XTAL2 не должна превышать 150 мА; для светодиодов от вывода к плюсу питания действует предел втекающего тока — 100 мА на группу. Через выводы VCC и GND — не больше 200 мА. Светодиод на 10 мА на каждом выводе укладывается в эти рамки; для мощной нагрузки нужен транзисторный ключ.

Источники

  1. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 14.2.1: три бита вывода DDxn, PORTxn, PINxn; DDxn = 1 — выход; при сбросе выводы в третьем состоянии; разд. 14.2.2: запись единицы в PINxn переключает PORTxn; разд. 14.2.4: чтение PINxn через синхронизатор; разд. 14.4: начальные значения DDRx и PORTx — 0, у PINx — N/A; разд. 29.1: 40 мА на вывод, 200 мА через VCC и GND; табл. 30-1, примечание 3: сумма вытекающих токов IOH выводов B0–B5, D5–D7, ADC6, XTAL1, XTAL2 не больше 150 мА; примечание 4: сумма втекающих токов по группам не больше 100 мА; VOL не больше 0,9 В при 20 мА и 5 В
  2. AVR-LibC 2.3.2: avr/sfr_defs.h и util/delay.h (github.com/avrdudes/avr-libc) — _BV(bit) = (1 << (bit)); F_CPU нужно задать до подключения util/delay.h; задержка должна быть константой времени компиляции, а оптимизация включена
  3. ArduinoCore-avr, variants/standard/pins_arduino.h (github.com/arduino/ArduinoCore-avr) — соответствие выводов Uno портам: 0–7 — порт D, 8–13 — порт B, A0–A5 — порт C; LED_BUILTIN — вывод 13

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