Управление портами микроконтроллеров 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 на языке С можно задать направление передачи данных сразу для всего порта.

Пример:

DDRB = 0xff;

С помощью этой команды все выводы (контакты) порта B будут сконфигурированы как выходы.

0xff представляет собой шестнадцатиричное представление числа ff, а 0x является префиксом, указывающим на то, что число записано в шестнадцатиричное форме. В десятичном представлении число 0xff будет равно 255, а в двоичном – 11111111. То есть с помощью представленной команды во все биты регистра DDRB будут записаны логические единицы.

В языке Си для микроконтроллеров AVR для представления двоичных чисел применяется префикс 0b. Соответственно, представленную выше команду записи логических единиц во все биты регистра DDRB можно записать и с помощью двоичного вида числа 255:

DDRD = 0b11111111;

Эта запись команды является более наглядной, но все таки правилом "хорошего тона" в программировании для микроконтроллеров считается использование шестнадцатиричного представления чисел.

Для того чтобы сконфигурировать все выводы (контакты) порта B как входы необходимо записать во все биты регистра DDRB логические нули. Это можно сделать с помощью следующей команды.

DDRB = 0x00;

Но кроме рассмотренных "крайних" случаев (все единицы или все нули) в регистр DDRB можно записать и другие числа. Например:

DDRB = 0xb4;

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. Для этой цели можно использовать следующую команду:

DDRD |= 1<<2;

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

1<<2 – с помощью этой части команды осуществляется сдвиг единички влево на 2 бита, то есть справа добавляются два нулевых бита и получается число 100 (в двоичном виде), а знак "|", стоящий перед знаком присваивания "=", выполняет операцию побитного логического сложения.

Операцию побитного логического сложения также называют операцией ИЛИ (английское название OR) и выполняется она по следующим правилам:
0+0=0
0+1=1
1+0=1
1+1=1

То есть если хотя бы одно из слагаемых равно 1, то результат также равен 1.

Таким образом, в результате данной операции (команда DDRD |= 1<<2) к битам регистра DDRD прибавится двоичное 100, представленное в 8-битном регистре микроконтроллера как 00000100, и результат будет записан я обратно в регистр DDRD. Схематично данная операция представлена на следующем рисунке.

Установка 1 на одном контакте порта D

Установка 0 в произвольном бите регистра порта

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

DDRD &= ~(1<<2);

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

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

Итак, в результате операции (1<<2) мы получили число 00000100, следовательно после проведения инверсии ("~") мы получим число 11111011.

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

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

То есть если хотя бы один из операндов операции равен 0, то и результат операции равен 0.

Таким образом, сдвинутая нами влево на две позиции единица (1<<2) превращается при инвертировании ("~") в ноль и умножается на соответствующий бит регистра DDRD, поэтому каким бы не было состояние данного бита, то при операции умножения на 0 данный бит становится равным 0. Схематично данный процесс представлен на следующем рисунке.

Установка 0 на одном контакте порта D

Установка значений на выводах порта

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

PORTx - регистр порта, где x обозначает имя порта.

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

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

Установить "1" на всех выводах (контактах) порта B можно c помощью следующей команды:

PORTB = 0xff.

Аналогично установка "0" на всех выводах порта B выполняется следующим образом:

PORTB = 0x00.

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

PORTB |= 1<<3;

установит "1" (сигнал высокого уровня) на контакте PB3.

Команда

PORTB &= ~(1<<4);

установит "0" (сигнал низкого уровня) на контакте PB4.

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

В этом случае две предыдущие команды можно записать следующим образом:

PORTB |= _BV(PB3); // установить "1" на линии 3 порта B

PORTB &= ~_BV(PB4); // установить "0" на линии 4 порта B

Пример программы зажигания светодиода, подключенного к выводу микроконтроллера AVR

Для лучшего понимания работы с портами микроконтроллеров AVR рассмотрим примеры простейших программ, осуществляющих включение и выключение светодиода, подключенного к выводу порта.

Светодиод к микроконтроллеру AVR можно подключить одним из следующих двух способов, представленных на рисунке.

Зажигание светодиода сигналом высокого уровня Зажигание светодиода сигналом низкого уровня

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

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

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

/*************************************************************************
ПРИМЕР ВКЛЮЧЕНИЯ СВЕТОДИОДА СИГНАЛОМ ВЫСОКОГО УРОВНЯ
Пример подключения на рисунке 1
**************************************************************************/

#include <avr/io.h> // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
int main(void) { // начало основной программы
DDRD = 0xff; // все выводы порта D сконфигурировать как выходы
PORTD |= 1<<1; // установить "1" (высокий уровень) на выводе PD1
} // закрывающая скобка основной программы

/***********************************************************************
ПРИМЕР ВКЛЮЧЕНИЯ СВЕТОДИОДА СИГНАЛОМ НИЗКОГО УРОВНЯ
Пример подключения на рисунке 2
************************************************************************/

#include <avr/io.h> // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
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, поэтому его нужно будет подключить к основной программе. Также для работы этой функции необходимо задать значение тактовой частоты микроконтроллера в Герцах (Гц).

/*************************************
ПРИМЕР МИГАНИЯ СВЕТОДИОДОМ
Пример подключения на рисунке 1
**************************************/

#define F_CPU 1000000UL // указываем тактовую частоту микроконтроллера в герцах
#include <avr/io.h> // заголовок чтобы задействовать функции контроля данных на выводах микроконтроллера
#include <util/delay.h> // заголовок чтобы задействовать функции задержки в программе

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 на языке С (Си) — 22 комментария

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

    • Наверное, нужно было сразу пояснить: я знаком с битовыми операциями, и знаю, как поменять состояние пина как вверх, так и вниз. Но перед этим потребуется определить текущее состояние пина, и только затем можно будет выполнить либо PORTB |= 1<<3;, либо PORTB &= ~(1<<3); (пример для пина PORTB.3). Т.е. перед каждымо "переворотом" состояния пина нужно будет выполнить несколько операций, которые занимают определенное время. Но эти действия требуется выполнять в прерывании таймера, а там это соизмеримо с периодом следования этих прерываний. Вот и возникла необходимость минимизировать время выполнения инверсии. Возможно, есть готовые решения, но я их, к сожалению, не знаю...

      • А почему для проверки текущего состояния пина нужно несколько операций? А команда по типу if (!bit_is_clear(PINA,5)) вам не подойдет. Примеры использования этой команды есть, к примеру, в этой статье.

  2. Помогите пожалуйста.
    У меня МК attiny13
    Как мне задать направление пинам PB0 - 4
    PB0 - вход
    PB1 - вход
    PB2 - выход
    PB3 - выход
    PB4 - выход

    Как я понимаю DDRB = 0x38 или DDRB = 0b00111000

  3. Подскажите пожалуйста я новичок и только изучаю как тут исправить ошибки?
    .include "m16def.inc" ; подключение библиотеки для работы с ATmega16
    clr r16 ; Очистка r16
    ser r17; Установка r17 (запись числа 111111112 в регистр) 255
    out DDRB, r17 ; направление передачи данных- на выход порта B
    nop ; Delay (пауза)
    ldi r16,0x1131 ; Запись числа 11310 в r16 113
    out PinB, r16 ; Запись данных из R16 (числа 11310) в порт B
    LOOP: // после выполнения всех команд
    jmp LOOP1 // запускается бесконечный цикл

    • Я вижу у вас программа на ассемблере, а я только с языком С работал поэтому в вашем случае, к сожалению, ничего дельного подсказать не могу

      • Добрый вечер. Я не знаю, к сожалению, актуален ли еще вопрос Ильи, но если вам не сложно напишите ответ на его комментарий. Ему на почту должно прийти сообщение о том, что к его комментарию появился новый ответ - возможно, благодаря этому он снова зайдет на наш сайт и тогда увидит ваш ответ на его сообщение

  4. АДМИН Здравствуйте ! У меня проблема с написанием прошивки для ATTINY2313A _ дело в том что у меня есть 6 разрядный семисигментный индикатор ! хочу сделать дублирующий табло для себя _ в микроконтроллерах у меня маленький опыт_ можете чем посоветовать ? я просто Не понимаю как сделать Так чтобы весы передавал инфо по кг на микроконтроллер а мК на на семисигментый индикатор ? Буду благодарен к любому совету !!! спасибо за ранее

  5. 0xb4 — шестнадцатиричное представление числа 180. В двоичном виде его можно записать как 10110100. То есть часть выводов (контактов) порта B будет сконфигурирована как выходы, а часть — как входы.

    Число 180, а не 190.

      • Почему должно получиться 179? Воспользуйтесь хотя бы калькулятором calculatori.ru/perevod-chisel.html - 180 получается. И если в самом младшем разряде двоичного числа стоит 0, то никак не может в десятичном виде 9 получиться - должно получиться четное число

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *