- Управление портами микроконтроллеров AVR на языке С (Си)
- Общие сведения о портах микроконтроллеров AVR
- Задание направления данных для всего порта
- Установка 1 в произвольном бите регистра порта
- Установка 0 в произвольном бите регистра порта
- Установка значений на выводах порта
- Пример программы зажигания светодиода, подключенного к выводу микроконтроллера AVR
- Комментарии
- Управление портами микроконтроллеров AVR на языке С (Си) — 21 комментарий
Управление портами микроконтроллеров AVR на языке С (Си)
В этой статье будет рассмотрено управление портами микроконтроллеров AVR на языке программирования С (Си): установка выводов порта на вход или выход, считывание значений на входах портов, программа для управления миганием светодиода.
Общие сведения о портах микроконтроллеров AVR
Порты микроконтроллеров AVR — это устройства ввода/вывода, позволяющие микроконтроллеру передавать или принимать данные. Стандартный порт микроконтроллера AVR содержит восемь разрядов данных, которые могут передаваться или приниматься параллельно. Ножки микроконтроллера также называют пинами, контактами или выводами. Порты обозначаются латинскими буквами А, В, С и т.д. Количество портов зависит от конкретной модели микроконтроллера.
Kонфигурирование каждой линии порта (задание направления передачи данных) может быть произведено программно в любой момент времени. Входные буферы портов построены по схеме триггера Шмитта. Для линий, сконфигурированных как входные, также имеется возможность подключения внутреннего подтягивающего резистора сопротивлением 35…120 кОм между входом и проводом питания. Kроме того, если вывод (вход) с подключенным внутренним подтягивающим резистором подключить к общему проводу, он может служить источником тока.
Обращение к портам производится через регистры ввода/вывода, причем под каждый порт в адресном пространстве ввода/вывода за-резервировано по 3 адреса. По этим адресам размещаются три регистра: регистр данных порта PORTx, регистр направления данных DDRx и регистр выводов порта PINx. Разряды этих регистров имеют названия: Px7…Px0 — для регистров PORTx, DDx7…DDx0 — для регистров DDRx и PINx7…PINx0 — для регистров PINx.
Действительные названия регистров (и их разрядов) получаются подстановкой названия порта вместо символа «x», соответственно для порта A ре¬гистры называются PORTA, DDRA, PINA, для порта B — PORTB, DDRB, PINB и т.д.
Следует заметить, что «регистры» PINx на самом деле регистрами не являются, по этим адресам осуществляется доступ к физическим значениям сигналов на выводах порта. Поэтому они доступны только для чтения, тогда как регистры PORTx и DDRx доступны и для чтения, и для записи.
Таким образом, запись в порт означает запись требуемого состояния для каждого вывода порта в соответствующий регистр данных порта PORTx. А чтение состояния порта выполняется чтением либо регистра данных порта PORTx, либо регистра выводов порта PINx. При чтении регистра выводов порта PINx происходит считывание логических уровней сигналов, присутствующих на выводах порта. А при чтении регистра данных порта PORTx происходит считывание данных, находящихся в регистре-защелке порта – это справедливо как для входных, так и для выходных контактов.
Любой порт микроконтроллера AVR можно сконфигурировать как вход или как выход. Для этой цели используется регистр DDRx. На вход или выход можно сконфигурировать сразу весь порт или только отдельный его вывод (контакт, пин).
Регистр DDRx определяет, является тот или иной вывод порта входом или выходом. Если некоторый разряд регистра DDRx содержит логическую единицу, то соответствующий вывод порта сконфигурирован как выход, в противном случае — как вход. Буква x в данном случае должна обозначать имя порта, с которым вы работаете. Таким образом, для порта A это будет регистр DDRA, для порта B — регистр DDRB и т. д.
Задание направления данных для всего порта
В программе для программирования микроконтроллеров AVR Atmel Studio на языке С можно задать направление передачи данных сразу для всего порта.
С помощью этой команды все выводы (контакты) порта B будут сконфигурированы как выходы.
0xff представляет собой шестнадцатиричное представление числа ff, а 0x является префиксом, указывающим на то, что число записано в шестнадцатиричное форме. В десятичном представлении число 0xff будет равно 255, а в двоичном – 11111111. То есть с помощью представленной команды во все биты регистра DDRB будут записаны логические единицы.
В языке Си для микроконтроллеров AVR для представления двоичных чисел применяется префикс 0b. Соответственно, представленную выше команду записи логических единиц во все биты регистра DDRB можно записать и с помощью двоичного вида числа 255:
Эта запись команды является более наглядной, но все таки правилом «хорошего тона» в программировании для микроконтроллеров считается использование шестнадцатиричного представления чисел.
Для того чтобы сконфигурировать все выводы (контакты) порта B как входы необходимо записать во все биты регистра DDRB логические нули. Это можно сделать с помощью следующей команды.
Но кроме рассмотренных «крайних» случаев (все единицы или все нули) в регистр DDRB можно записать и другие числа. Например:
0xb4 — шестнадцатиричное представление числа 180. В двоичном виде его можно записать как 10110100. То есть часть выводов (контактов) порта B будет сконфигурирована как выходы, а часть — как входы.
PB0 — 0 (вход)
PB1 — 0 (вход)
PB2 — 1 (выход)
PB3 — 0 (вход)
PB4 — 1 (выход)
PB5 — 1 (выход)
PB6 — 0 (вход)
PB7 — 1 (выход)
Установка 1 в произвольном бите регистра порта
Каждый бит регистров DDRx может быть установлен или сброшен отдельно. К примеру, если мы хотим сконфигурировать отдельно вывод PD2 как выход, то нам следует в соответствующий бит регистра DDRD записать 1. Для этой цели можно использовать следующую команду:
На первый взгляд она может показаться слишком сложной и запутанной, но после объяснения этой команды вы поймете, что все в ней выглядит достаточно логично.
То есть если хотя бы одно из слагаемых равно 1, то результат также равен 1.
Установка 0 в произвольном бите регистра порта
Если мы хотим сконфигурировать отдельно вывод (контакт) PD2 как вход, то необходимо в соответствующий бит регистра DDRD записать 0. Для этого можно использовать следующую команду.
В этом случае результат сдвига единицы на две позиции влево инвертируется с помощью операции побитного инвертирования, которая в языке С обозначаемой знаком «~».
В результате операции инверсии мы получаем вместо нулей единицы, а вместо единиц — нули. Данная логическая операция также называется операцией НЕ (английское название NOT).
Получившееся число при помощи операции побитного логического умножения & умножается на число, хранящееся в регистре DDRD, и результат затем записывается в регистр DDRD.
Логическое умножение, по другому называемое операцией И (английское название AND), выполняется по следующим правилам:
0*0=0
0*1=0
1*0=0
1*1=1
То есть если хотя бы один из операндов операции равен 0, то и результат операции равен 0.
Установка значений на выводах порта
После того как направление передачи данных у порта будет сконфигурировано, можно присвоить порту значение, которое будет храниться в соответствующем регистре PORTx.
PORTx — регистр порта, где x обозначает имя порта.
Если вывод (контакт) сконфигурирован как выход, то единица в соответствующем бите регистра PORTx формирует на выводе сигнал высокого уровня, а ноль — сигнал низкого уровня.
Если вывод (контакт) сконфигурирован как вход, то единица в бите регистра PORTx подключает к выводу внутренний подтягивающий pull-up резистор, который обеспечивает высокий уровень на входе при отсутствии внешнего сигнала.
Установить «1» на всех выводах (контактах) порта B можно c помощью следующей команды:
Аналогично установка «0» на всех выводах порта B выполняется следующим образом:
К каждому биту регистров PORTx можно обращаться и по отдельности так же, как и в рассмотренном выше случае с регистрами DDRx. К примеру, команда
установит «1» (сигнал высокого уровня) на контакте PB3.
установит «0» (сигнал низкого уровня) на контакте PB4.
В программе Atmel Studio сдвиг можно выполнять и с помощью функции _BV(), которая производит поразрядный сдвиг и помещает результат в компилируемый код.
В этом случае две предыдущие команды можно записать следующим образом:
PORTB |= _BV(PB3); // установить «1» на линии 3 порта B
PORTB &= ~_BV(PB4); // установить «0» на линии 4 порта B
Пример программы зажигания светодиода, подключенного к выводу микроконтроллера AVR
Для лучшего понимания работы с портами микроконтроллеров AVR рассмотрим примеры простейших программ, осуществляющих включение и выключение светодиода, подключенного к выводу порта.
Светодиод к микроконтроллеру AVR можно подключить одним из следующих двух способов, представленных на рисунке.
В первом случае (рисунок слева) светодиод будет загораться от сигнала высокого уровня на выводе PD1, а во втором случае (рисунок справа) — от сигнала низкого уровня на этом же контакте.
В зависимости от способа подключения светодиод будет загораться либо от сигнала высокого уровня, подаваемого на вывод PD1 микроконтроллера, как в первом случае, либо от сигнала низкого уровня в случае подключения, изображенного на втором рисунке.
Теперь рассмотрим примеры программ, реализующих данные способы включения светодиода.
#include // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
int main(void) < // начало основной программы
DDRD = 0xff; // все выводы порта D сконфигурировать как выходы
PORTD |= 1 > // закрывающая скобка основной программы
#include // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
int main(void) < // начало основной программы
DDRD = 0xff; // все выводы порта D сконфигурировать как выходы
PORTD &= ~_BV(PD1); // установить «0» (низкий уровень) на выводе PD1
> // закрывающая скобка основной программы
Теперь для случая, представленного на рисунке 1, попробуем мигнуть светодиодом. Для этой цели воспользуемся функцией задержки _delay_ms() .
Функция _delay_ms() формирует задержку в зависимости от передаваемого ей аргумента, выраженного в миллисекундах (в одной секунде 1000 миллисекунд). Максимальная задержка составляет 262.14 миллисекунд. Если передать функции значение более 262.14, то осуществится автоматическое уменьшение разрешения до 1/10 миллисекунды, что обеспечивает задержки до 6.5535 секунд. Более длительные задержки можно реализовать с помощью циклов в программе.
Функция _delay_ms() содержится в файле delay.h, поэтому его нужно будет подключить к основной программе. Также для работы этой функции необходимо задать значение тактовой частоты микроконтроллера в Герцах (Гц).
#define F_CPU 1000000UL // указываем тактовую частоту микроконтроллера в герцах
#include // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
#include // заголовок чтобы задействовать функции задержки в программе
int main(void) < // начало основной программы
DDRD = 0xff; // все выводы порта D сконфигурировать как выходы
PORTD |= _BV(PD1); // установить «1» (высокий уровень) на выводе PD1,
//зажечь светодиод
_delay_ms(500); // ждем 0.5 сек.
PORTD &= ~_BV(PD1); // установить «0» (низкий уровень) на выводе PD1,
//погасить светодиод
_delay_ms(500); // ждем 0.5 сек.
PORTD |= _BV(PD1); // установить «1» (высокий уровень) на выводе PD1,
//зажечь светодиод
_delay_ms(500); // ждем 0.5 сек.
PORTD &= ~_BV(PD1); // установить «0» (низкий уровень) на выводе PD1,
//погасить светодиод
> // закрывающая скобка основной программы
В представленной программе светодиод мигнет всего 2 раза. Чтобы он мигал непрерывно можно организовать бесконечный цикл с помощью оператора безусловного перехода » goto «. Данный оператор выполняет переход к месту программы, обозначенному меткой. Но лучше это реализовать с помощью бесконечного цикла на основе оператора while – пример работающей схемы для этого случая и программы для нее можно посмотреть в этой статье на нашем сайте.
Комментарии
Управление портами микроконтроллеров AVR на языке С (Си) — 21 комментарий
Подскажите, плз, как выполнить инверсию пина, настроенного как выход, не зависимо от его текущего состояния.
Наверное, нужно было сразу пояснить: я знаком с битовыми операциями, и знаю, как поменять состояние пина как вверх, так и вниз. Но перед этим потребуется определить текущее состояние пина, и только затем можно будет выполнить либо PORTB |= 1
А почему для проверки текущего состояния пина нужно несколько операций? А команда по типу if (!bit_is_clear(PINA,5)) вам не подойдет. Примеры использования этой команды есть, к примеру, в этой статье.
Помогите пожалуйста.
У меня МК attiny13
Как мне задать направление пинам PB0 — 4
PB0 — вход
PB1 — вход
PB2 — выход
PB3 — выход
PB4 — выход Как я понимаю DDRB = 0x38 или DDRB = 0b00111000
Извините, не могу временно помочь. У меня переезд, времени для работы над сайтом совсем нет, захожу на сайт с телефона