Порты и регистры: мигаем светодиодом
Каждый вывод 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 — номер бита:
| Регистр | Бит | Что делает |
|---|---|---|
| DDRx | DDxn | направление: 1 — выход, 0 — вход |
| PORTx | PORTxn | у выхода — уровень (1 — высокий, 0 — низкий); у входа — 1 включает подтягивающий резистор |
| PINx | PINxn | чтение уровня на выводе; запись 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 и GND | 200 мА | 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 |
Абсолютный максимум — это не рабочий режим, а граница повреждения. Нормальная нагрузка на вывод — единицы миллиампер; для реле или двигателя нужен транзисторный ключ.
Задание
- Какое число надо записать в DDRB одной операцией
DDRB |= …, чтобы сделать выходами PB0, PB1 и PB5 (выводы 8, 9 и 13 Uno)? Ответ дайте в шестнадцатеричном виде. - Перепишите цикл мигания так, чтобы светодиоды на PB5 и PD7 горели по очереди.
Ответ и решение
-
Маска
_BV(0) | _BV(1) | _BV(5)= 0b00100011 = 0x23. -
Один из вариантов:
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 мА на каждом выводе укладывается в эти рамки; для мощной нагрузки нужен транзисторный ключ.
Источники
- 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 В
- 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; задержка должна быть константой времени компиляции, а оптимизация включена
- 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
Нашли ошибку?
Смотрите также
- Битовые операции в CМаски, сдвиги, установка и проверка битов, ловушки стандарта C
- ATmega328PРаспиновка, фьюзы, Optiboot, периферия, питание, схема и код на C
- Резистор для светодиодаНоминал и мощность гасящего резистора
- Задержки AVR: _delay_msТакты и фактическое время _delay_ms, _delay_us, циклов; пределы и округление
- Почему светодиод на выводе AVR горит тускло?Забыт DDRx или pinMode: светодиод питается через подтяжку 20–50 кОм