Управление портами через регистры atmega. Регистры и порты микроконтроллера AVR. _______________поразрядная инверсия ~ _______________

При программировании микроконтроллеров постоянно приходится работать с битами. Устанавливать их, сбрасывать, проверять их наличие в том или ином регистре. В AVR ассемблере для этих целей существует целый ряд команд. Во-первых, это группа команд операций с битами – они предназначены для установки или сброса битов в различных регистрах микроконтроллера, а во-вторых, группа команд передачи управления – они предназначены для организации ветвлений программ. В языке Си естественно нет подобных команд, поэтому у начинающих программистов часто возникает вопрос, а как в Си работать с битами. Эту тему мы сейчас и будем разбирать.

В Си существуют 6 операторов для манипулирования битами. Их можно применять к любым целочисленным знаковым или беззнаковым типам переменных.

<< - сдвиг влево
>> - сдвиг вправо
~ - поразрядная инверсия
| - поразрядное ИЛИ
& - поразрядное И
^ - поразрядное исключающее ИЛИ

_______________ сдвиг влево << _______________

Сдвигает число на n разрядов влево. Старшие n разрядов при этом исчезают, а младшие n разрядов заполняются нулями.


unsigned char tmp = 3; //0b00000011
tmp = tmp << 1;
//теперь в переменной tmp число 6 или 0b00000110

Tmp = tmp << 3;
//теперь в переменной tmp число 48 или 0b00110000

Выражения, в которых над переменной производится какая-либо операция, а потом результат операции присваивается этой же переменной, можно записывать короче, используя составные операторы.

Tmp = 7; //0b00000111
tmp <<= 2; //сокращенный вариант записи
//теперь в переменной tmp число 28 или 0b00011100

Операция сдвига влево на n разрядов эквивалентна умножению переменной на 2 n .

_______________ сдвиг вправо >> _______________

Сдвигает число на n разрядов вправо. Младшие n разрядов при этом теряются. Заполнение старших n разрядов зависит от типа переменной и ее значения. Старшие n разрядов заполняются нулями в двух случаях – если переменная беззнакового типа или если переменная знаковая и ее текущее значение положительное. Когда переменная знаковая и ее значение отрицательное – старшие разряды заполняются единицами.

Пример для беззнаковой переменной

unsigned char tmp = 255; //0b11111111
tmp = tmp >> 1;
//теперь в переменной tmp число 127 или 0b01111111

Tmp >>= 3; //сокращенный вариант записи
//теперь в переменной tmp число 15 или 0b00001111

Пример для переменной знакового типа

int tmp = 3400; //0b0000110101001000
tmp >>= 2;
//теперь в переменной число 850 или 0b0000001101010010

Tmp = -1200; //0b1111101101010000
tmp >>= 2;
//теперь в tmp число -300 или 0b1111111011010100
//видите - два старших разряда заполнились единицами

Операция сдвига вправо на n разрядов эквивалентна делению на 2 n . При этом есть некоторые нюансы. Если потерянные младшие разряды содержали единицы, то результат подобного “деления” получается грубоватым.

Например 9/4 = 2,5 а 9>>2 (1001>>2) равно 2
11/4 = 2,75 а 11>>2 (1011>>2) равно 2
28/4 = 7 а 28>>2 (11100>>2) равно 7

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

_______________поразрядная инверсия ~ _______________

Поразрядно инвертирует число. Разряды, в которых были нули – заполняются единицами. Разряды, в которых были единицы – заполняются нулями. Оператор поразрядной инверсии являтся унарным оператором, то есть используется с одним операндом.

unsigned char tmp = 94; //0b01011110
tmp = ~tmp;
//теперь в переменной tmp число 161 или 0b10100001

Tmp = ~tmp;
//теперь в tmp снова число 94 или 0b01011110

_______________ поразрядное ИЛИ | ______________

Оператор | осуществляет операцию логического ИЛИ между соответствующими битами двух операндов. Результатом операции логического ИЛИ между двумя битами будет 0 только в случае, если оба бита равны 0. Во всех остальных случаях результат будет 1. Это проиллюстрировано в табице истинности.

Оператор | обычно используют для установки заданных битов переменной в единицу.

Tmp = 155
tmp = tmp | 4; //устанавливаем в единицу второй бит переменной tmp

155 0b100110 11
4 0b000001 00
159 0b100111 11

Использовать десятичные числа для установки битов довольно неудобно. Гораздо удобнее это делать с помощью операции сдвига влево <<.


tmp = tmp | (1<<4); //устанавливаем в единицу четвертый бит переменной tmp

Читаем справа налево – сдвинуть единицу на четыре разряда влево, выполнить операцию ИЛИ между полученным числом и значением переменной tmp, результат присвоить переменной tmp.


Установить несколько битов в единицу можно так

Tmp = tmp | (1<<7)|(1<<5)|(1<<0);
//устанавливаем в единицу седьмой, пятый и нулевой биты переменной tmp

С помощью составного оператора присваивания |= можно сделать запись компактней.

Tmp |= (1<<4);
tmp |= (1<<7)|(1<<5)|(1<<0);

_______________ побитовое И & _______________

Оператор & осуществляет операцию логического И между соответствующими битами двух операндов. Результатом операции логического И между двумя битами будет 1 только в том случае, если оба бита равны 1. Во всех других случаях результат будет 0. Это проиллюстрировано в таблице истинности.

Оператор & обычно применяют, чтобы обнулить один или несколько битов.

Tmp = 155;
tmp = tmp & 247; //обнуляем третий бит переменной tmp

155 0b10011 011
&
247 0b11110 111
147 0b10010 011

Видите, третий бит стал равен 0, а остальные биты не изменились.

Обнулять биты, используя десятичные цифры, неудобно. Но можно облегчить себе жизнь, воспользовавшись операторами << и ~

Tmp = 155;
tmp = tmp & (~(1<<3)); //обнуляем третий бит

1<<3 0b00001 000
~(1<<3) 0b11110 111
tmp & (~(1<<3)) 0b10011 011 & 0b11110 111
результат 0b10010 011

Читаем справа налево – сдвинуть единицу на три разряда влево, выполнить инверсию полученного числа, выполнить операцию & между значением переменной tmp и проинвертированным числом, результат присвоить переменной tmp.


Обнулить несколько битов можно так

tmp = tmp & (~((1<<3)|(1<<5)|(1<<6))); //обнуляем третий, пятый и шестой биты



Используя составной оператор присваивания &= ,можно записать выражение более компактно

Tmp &= (~((1<<3)|(1<<5)|(1<<6)));

Как проверить установлен ли бит в переменной?
Нужно обнулить все биты, кроме проверочного, а потом сравнить полученное значение с нулем

if ((tmp & (1<<2)) != 0){
// блок будет выполняться, только если установлен

}

if ((tmp & (1<<2)) == 0){
// блок будет выполняться, только если не установлен
// второй бит переменной tmp

}

_______________побитовое исключающее ИЛИ ^ _______________


Оператор ^ осуществляет операцию логического исключающего ИЛИ между соответствующими битами двух операндов. Результатом операции логического исключающего ИЛИ будет 0 в случае равенства битов. Во всех остальных случаях результат будет 1. Это проиллюстрировано в табице истинности.

Оператор ^ применяется не так часто как остальные битовые операторы, но и для него находится работенка. Например, с помощью него можно инвертировать один или несколько битов переменной .


tmp = 155;
tmp = tmp ^ 8; // инвертируем четвертый бит переменой tmp

155 0b10011 011
^
8 0b00001 000
147 0b10010 011

Четвертый бит изменил свое значение на противоположное, а остальные биты остались без изменений.

Tmp = tmp ^ 8; // опять инвертируем четвертый бит переменой tmp

147 0b10010 011
^
8 0b0000 1 000
155 0b10011 011

Видите, четвертый бит снова изменил свое значение на противоположное.

Так записывать выражение намного удобнее

Tmp = tmp ^ (1<<3); // инвертируем третий бит переменой tmp

А так и удобно и компактно

Tmp ^= (1<<4); //инвертируем четверый бит

Можно инвертировать несколько битов одновременно

Tmp ^= ((1<<4)|(1<<2)|(1<<1)); //инвертируем 4,2 и 1 биты

У поразрядного исключающего ИЛИ есть еще одно интересное свойство . Его можно использовать, для того чтобы поменять значения двух переменных местами. Обычно для этого требуется третья переменная.


tmp = var1;
var1 = var2;
var2 = tmp;

Но используя оператор ^ переставить значения можно так:

var1 ^= var 2;
var 2 ^= var 1;
var 1 ^= var 2;

Чистая магия, хотя, честно говоря, я ни разу не пользовался таким приемом.

________________Директива #define__________________


Теперь мы знаем, как устанавливать, обнулять и инвертировать биты, знаем, как проверять установлен ли бит или нет. Рассмотренные выше выражения довольно громоздки, но с помощью директивы препроцессора #define , им можно придать более приятный вид.

Директива #define используется для присваивания символических имен константам и для макроопределений. Использование символических имен делают программу более модифицируемой и переносимой.

Например, вы используете в тексте программы константу, и вдруг вам понадобилось изменить ее значение. Если она встречается всего в трех местах, то исправить ее можно и в ручную, а что делать, если она встречается в пятидесяти строчках? Мало того, что исправление займет много времени, так еще и ошибиться в этом случае проще простого. Здесь то, как раз и выручает директива #define . В начале программы задается символическое имя константы, которое используется по ходу программы. Если нам нужно изменить это значение, это делается всего лишь в одном месте. А перед компиляцией препроцессор сам подставит во все выражения вместо имени константы ее значение.

Программирование микроконтроллера неразрывно связано с его аппаратной частью и чаще всего с внешней обвязкой. Взять хотя бы кнопки - опрашивая их в своей программе, мы обращаемся к реальным выводам микроконтроллера. А если нам вдруг понадобилось использовать программу опроса кнопок в другой схеме, где кнопки подключены к другим выводам? Придется исправлять программу. Опять таки, задав с помощью директивы #define символическое имя для соответствующих выводов, модифицировать программу будет проще простого


Пример:

#include "iom8535.h"

//порт, к которому подключены кнопки
#define PORT_BUTTON PORTA
#define PIN_BUTTON PINA
#define DDRX_BUTTON DDRA

//выводы, к которым подключены кнопки
#define DOWN 3
#define CANCEL 4
#define UP 5
#define ENTER 6

int main()
{
//конфигурируем порт на вход,
//и включаем подтягивающие резисторы

DDRX_BUTTON = 0;
PORT_BUTTON = 0xff;

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

#define MASK_BUTTONS ((1<

пример использования:
tmp = PORTB & MASK_BUTTONS;

Используя #define не жалейте скобок чтобы четко задать последовательность вычисления выражений!

Некоторые выражения можно замаскировать под «функции».

#define ADC_OFF() ADCSRA = 0

пример использования:
ADC_OFF();

Можно использовать многострочные определения, используя в конце каждой строки символ \

#define INIT_Timer() TIMSK = (1< TCCR0 = (1< TCNT0 = 0;\
OCR0 = 0x7d

пример использования:
INIT_Timer();

Ну и самое мощное применение директивы #define – это задание макроопределений (или просто макросов). Вот как с помощью #define можно задать макросы для рассмотренных ранее операций с битами

#define SetBit(reg, bit) reg |= (1<#define ClearBit(reg, bit) reg &= (~(1<#define InvBit(reg, bit) reg ^= (1<#define BitIsSet(reg, bit) ((reg & (1<#define BitIsClear(reg, bit) ((reg & (1<

пример использования:

SetBit(PORTB, 0); //установить нулевой бит порта B
InvBit(tmp,6); //инвертировать шестой бит переменной tmp


if (BitIsClear(PIND, 0)) { //если очищен нулевой бит в регистре PIND
….. //выполнить блок
}

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

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

Определим макрос, вычисляющий квадрат числа:

#define SQUARE(x) x*x

выражение
tmp = SQUARE(my_var);
даст корректный результат.

А что будет если в качестве аргумента макроопределения использовать выражение my_var+1

tmp = SQUARE(my_var +1);

Препроцессор заменит эту строчку на

tmp = my_var + 1 * my_var +1;

а это вовсе не тот результат, который мы ожидаем.

Чтобы избежать таких ошибок не скупитесь на скобки при объявлении макросов!

Если объявить макрос так

#define SQUARE(x) ((x)*(x))

выражение
tmp = SQUARE(my_var +1);
даст корректный результат, потому что препроцессор заменит эту строчку на
tmp = ((my_var + 1) * (my_var +1));

записываем их в папку проекта, а в начале файла main.c прописываем #include "bits_macros.h"

  • Tutorial

Работа портов ввода/вывода

Изучив данный материал, в котором все очень детально и подробно описано с большим количеством примеров, вы сможете легко овладеть и программировать порты ввода/вывода микроконтроллеров AVR.

Пример будем рассматривать на микроконтроллере ATMega8 .

Программу писать будем в Atmel Studio 6.0 .

Эмулировать схему будем в Proteus 7 Professional .

С внешним миром микроконтроллер общается через порты ввода вывода. Схема порта ввода вывода указана в даташите:

Но новичку разобраться довольно со схемой довольно сложно. Поэтому схему упростим:

Каждый порт микроконтроллера AVR (обычно имеют имена A, B и иногда C или даже D) имеет 8 разрядов, каждый из которых привязан к определенной ножке корпуса. Каждый порт имеет три специальных регистра DDRx , PORTx и PINx (где x соответствует букве порта A, B, C или D). Назначение регистров:

DDRx – Настройка разрядов порта x на вход или выход.

PORTx – Управление состоянием выходов порта x (если соответствующий разряд настроен как выход), или подключением внутреннего pull-up резистора (если соответствующий разряд настроен как вход).

PINx –Чтение логических уровней разрядов порта x.

PINхn – это регистр чтения. Из него можно только читать. В регистре PINxn содержится информация о реальном текущем логическом уровне на выводах порта. Вне зависимости от настроек порта. Так что если хотим узнать что у нас на входе - читаем соответствующий бит регистра PINxn . Причем существует две границы: граница гарантированного нуля и граница гарантированной единицы - пороги за которыми мы можем однозначно четко определить текущий логический уровень. Для пятивольтового питания это 1.4 и 1.8 вольт соответственно. То есть при снижении напряжения от максимума до минимума бит в регистре PINx переключится с 1 на 0 только при снижении напряжение ниже 1.4 вольт, а вот когда напряжение нарастает от минимума до максимума переключение бита с 0 на 1 будет только по достижении напряжения в 1.8 вольта. То есть возникает гистерезис переключения с 0 на 1, что исключает хаотичные переключения под действием помех и наводок, а также исключает ошибочное считывание логического уровня между порогами переключения.

При снижении напряжения питания разумеется эти пороги также снижаются.

DDRxn – это регистр направления порта. Порт в конкретный момент времени может быть либо входом либо выходом (но для состояния битов PINxn это значения не имеет. Читать из PINxn реальное значение можно всегда).

DDRxy = 0 – вывод работает как ВХОД.

DDRxy = 1 – вывод работает на ВЫХОД.

PORTxn – режим управления состоянием вывода. Когда мы настраиваем вывод на вход, то от PORTх зависит тип входа (Hi-Z или PullUp, об этом чуть ниже).

Когда ножка настроена на выход, то значение соответствующего бита в регистре PORTx определяет состояние вывода. Если PORTxn =1 то на выводе лог.1, если PORTxn =0 то на выводе лог.0.

Когда ножка настроена на вход, то если PORTxn =0, то вывод в режиме Hi-Z. Если PORTxn =1 то вывод в режиме PullUpс подтяжкой резистором в 100к до питания.

Таблица. Конфигурация выводов портов.

DDRxn PORTxn I/O Comment
0 0 I (Input) Вход Высокоимпендансный вход. (Не рекомендую использовать, так как могут наводится наводки от питания)
0 1 I (Input) Вход Подтянуто внутренне сопротивление.
1 0 O (Output) Выход На выходе низкий уровень.
1 1 O (Output) Выход На выходе высокий уровень.

Общая картина работы порта показана на рисунках:

Рис. DDRxn =0 PORTxn =0 – Режим: HI-Z – высоко импендансный вход.

Рис. DDRxn =0 PORTxn =1 – Режим: PullUp – вход с подтяжкой до лог.1.

Рис. DDRxn =1 PORTxn =0 – Режим: Выход – на выходе лог.0. (почти GND)

Рис. DDRxn =1 PORTxn =1 – Режим: Выход – на выходе лог.1. (почти VCC)

Вход Hi-Z - режим высокоимпендансного входа.
Этот режим включен по умолчанию. Все ключи разомкнуты, а сопротивление порта очень велико. В принципе, по сравнению с другими режимами, можно его считать бесконечностью. То есть электрически вывод как бы вообще никуда не подключен и ни на что не влияет. Но! При этом он постоянно считывает свое состояние в регистр PINn и мы всегда можем узнать что у нас на входе - единица или ноль. Этот режим хорош для прослушивания какой либо шины данных, т.к. он не оказывает на шину никакого влияния. А что будет если вход висит в воздухе? А в этом случае напряжение будет на нем скакать в зависимости от внешних наводок, электромагнитных помех и вообще от фазы луны и погоды на Марсе (идеальный способ нарубить случайных чисел!). Очень часто на порту в этом случае нестабильный синус 50Гц - наводка от сети 220В, а в регистре PINn будет меняться 0 и 1 с частотой около 50Гц

Вход PullUp - вход с подтяжкой.
При DDRxn =0 и PORTxn =1 замыкается ключ подтяжки и к линии подключается резистор в 100кОм, что моментально приводит не подключенную никуда линию в состояние лог.1. Цель подтяжки очевидна - не допустить хаотичного изменения состояния на входе под действием наводок. Но если на входе появится логический ноль (замыкание линии на землю кнопкой или другим микроконтроллером/микросхемой), то слабый 100кОмный резистор не сможет удерживать напряжение на линии на уровне лог.1 и на входе будет лог.0.

Режим выхода.
Тут, думаю, все понятно - если нам надо выдать в порт лог.1, мы включаем порт на выход (DDRxn =1) и выдаем лог.1 (PORTxn =1) - при этом замыкается верхний ключ и на выводе появляется напряжение, близкое к питанию. А если надо лог.0, то включаем порт на выход (DDRxn =1) и выдаем лог.0 (PORTxn =1) - при этом открывается уже нижний вентиль, что дает на выводе около нуля вольт.

Бит

Чтение/Запись

Исходное значение

· Бит 7 – Разрешение всех прерываний. Для разрешения прерываний этот бит должен быть установлен в состояние 1. Управление разрешением конкретного прерывания выполняется регистром маски прерывания EIMSK и TIMSK. Если этот бит очищен (=0), то ни одно из прерываний не обрабатывается. Бит аппаратно очищается после возникновения прерывания и устанавливается для последующего разрешения прерывания командой RETI.
· Бит 6 – Бит сохранения копии. Команды копирования бита BLD и BST используют этот бит как источник и приемник при операциях с битами. Командой BST бит регистра общего назначения копируется в бит T, командой BLD бит Т копируется в бит регистра общего назначения.
· Бит 5 – Флаг полупереноса. Он указывает на перенос между тетрадами при выполнении ряда арифметических операций.
· Бит 4 – Бит знака. Бит S имеет значение результата операции исключающее ИЛИ (N(+)V) над флагами отрицательного значения (N) и дополнения до двух флага переполнения (V).

· Бит 3 – Дополнение до двух флага переполнения. Он поддерживает арифметику дополнения до двух.
· Бит 2 – Флаг отрицательного значения. Этот флаг указывает на отрицательный результат ряда арифметических и логических операций.
· Бит 1 – Флаг нулевого значения. Этот флаг указывает на нулевой результат ряда арифметических и логических операций.
· Бит 0 – Флаг переноса. Этот флаг указывает на перенос при арифметических и логических операциях.

Микроконтроллер AT90S8535 имеет 4 параллельных порта ввода/вывода A, B,C и D.
Порт А является 8-разрядным двунаправленным портом. Взаимодействие с портом А осуществляется через три регистра в пространстве ввода/вывода памяти данных: регистр данных – PORTA, $1B($3B), регистр направления данных – DDRA, $1A($3A), регистр входных данных – PINA, $19($39). Регистр PINA обеспечивает только возможность чтения, а регистры PORTA и DDRA – возможность чтения и записи. Регистр PINA не является регистром в полном смысле этого слова. Обращение к нему обеспечивает чтение физического состояния каждого вывода порта. Порт А служит также для ввода аналоговых сигналов A/D.

Регистр данных порта А – PORTA

Бит

Чтение/Запись

Исходное значение

Регистр направления данных порта А – DDRA

Бит

Чтение/Запись

Исходное значение

Регистр входных данных порта А – PINA

Бит

Чтение/Запись

Исходное значение

Порт В является 8-разрядным двунаправленным портом ввода/вывода. Также как и у порта А взаимодействие с портом В осуществляется через три регистра в пространстве ввода/вывода памяти данных: регистр данных – PORTB, $18($38), регистр направления данных – DDRB, $17($37) и регистр входных данных – PINB, $16($36). Регистр PINB обеспечивает возможность только чтения. Регистр PINB не является регистром в полном смысле этого слова. Обращение к нему обеспечивает чтение физического состояния каждого вывода порта. Выводы порта В могут выполнять альтернативные функции, указанные в табл. 2.1.

Таблица 2.1. Альтернативные функции выводов порта В

Вывод порта

Альтернативная функция

T0 – вход тактового сигнала таймера/счетчика 0

T1 – вход тактового сигнала таймера/счетчика 1

AIN0 – положительный вывод компаратора

AIN1 – отрицательный вывод компаратора

– вход выбора ведомого SPI

MOSI – установка ведущий выход/ведомый вход SPI

MISO – установка ведущий вход/ведомый выход SPI

SCK – тактовый сигнал SPI

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

Регистр данных порта B PORTB

Бит

Чтение/Запись

Исходное значение

Регистр направления данных порта B – DDRB

Бит

Чтение/Запись

Исходное значение

Регистр входных данных порта B – PINB

Бит

Чтение/Запись

Исходное значение

Порт С представляет собой 8-разрядный двунаправленный порт ввода/вывода. Также как у портов А и В взаимодействие с портом С осуществляется через три регистра в пространстве ввода/вывода памяти данных: регистр данных – PORTC, $15($35), регистр направления данных – DDRC, $14($34) и регистр входных данных – PINC, $13($33). Регистр PINC обеспечивает только возможность чтения, а регистры PORTC и DDRC – возможность чтения и записи. Регистр PINC не является регистром в полном смысле этого слова. Обращение к нему обеспечивает чтение физического состояния каждого вывода порта.
У порта С только два вывода могут выполнять альтернативные функции: выводы PC6 и PC7 выполняют функции TOSC1 и TOSC2 таймера/счетчика 2.

Регистр данных порта C PORTC

Бит

Чтение/Запись

Исходное значение

Регистр направления данных порта C – DDRC

Бит

Чтение/Запись

Исходное значение

Регистр входных данных порта C – PINC

Бит

Чтение/Запись

Исходное значение

Порт D является 8-разрядным двунаправленным портом ввода/вывода. Также как и у портов А, В и С взаимодействие с портом D осуществляется через три регистра в пространстве ввода/вывода памяти данных: регистр данных – PORTD, $12($32), регистр направления данных – DDRD, $11($31) и регистр входных данных – PIND, $10($30). Регистр PIND обеспечивает возможность чтения, а регистры PORTD и DDRD – возможность чтения и записи. Регистр PIND не является регистром в полном смысле этого слова. Обращение к нему обеспечивает чтение физического состояния каждого вывода порта.
Выводы порта D могут выполнять альтернативные функции, указанные в табл. 2.2.

Таблица 2.2. Альтернативные функции выводов порта D

Вывод порта

Альтернативная функция

RxD – вход приемника UART

TxD – выход передатчика UART

INT0 – вход внешнего прерывания 0

INT1 – вход внешнего прерывания 1

OC1B – вывод сравнения выхода В таймера/счетчика 1

OC1А – вывод сравнения выхода А таймера/счетчика 1

ICP – вход триггера захвата таймера/счетчика 1

OC2 – вывод сравнения выхода таймера/счетчика 2

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

Регистр данных порта D PORTD

Бит

Чтение/Запись

Исходное значение

Регистр направления данных порта D DDRD

Бит

Чтение/Запись

Исходное значение

Регистр входных данных порта D PIND

Бит

Чтение/Запись

Исходное значение

Так как рассматриваемая работа первая, то для приобретения обучающимися навыков работы с лабораторным комплексом все обучающиеся сначала делают одинаковую работу. Со своих рабочих мест они вводят в ПЭВМ одну и ту же задачу вычитания из числа 5 числа 3, приведённую в п. 1.5.3.1. После компиляции программы она записывается в микроконтроллер рабочего места и демонстрируется её работа преподавателю.
После такого знакомства с комплексом обучающийся приступает к выполнению индивидуального задания. При наличии времени преподаватель может усложнить индивидуальное задание.

Введение

Все порты ввода-вывода (ПВВ) AVR-микроконтроллеров работают по принципу чтение-модификация-запись при использовании их в качестве портов универсального ввода-вывода. Это означает, что изменение направления ввода-вывода одной линии порта командами SBI и CBI будет происходит без ложных изменений направления ввода-вывода других линий порта. Данное распространяется также и на изменение логического уровня (если линия порта настроена на вывод) или на включение/отключение подтягивающих резисторов (если линия настроена на ввод). Каждый выходной буфер имеет симметричную характеристику управления с высоким втекающим и вытекающим выходными токами. Выходной драйвер обладает нагрузочной способностью, которая позволяет непосредственно управлять светодиодными индикаторами. Ко всем линиям портов может быть подключен индивидуальный выборочный подтягивающий к плюсу питания резистор, сопротивление которого не зависит от напряжения питания. На всех линиях ПВВ установлены защитные диоды, которые подключены к VCC и Общему (GND), как показано на рисунке 29. Подробный перечень параметров ПВВ приведен в разделе "Электрические характеристики".

Рисунок 29 – Эквивалентная схема линии ПВВ

Ссылки на регистры и биты регистров в данном разделе даны в общей форме. При этом, символ “x” заменяет наименование ПВВ, а символ “n” заменяет номер разряда ПВВ. Однако при составлении программы необходимо использовать точную форму записи. Например, PORTB3, означающий разряд 3 порта B, в данном документе записывается как PORTxn. Адреса физических регистров ввода-вывода и распределение их разрядов приведены в разделе “Описание регистров портов ввода-вывода".

Для каждого порта ввода-вывода в памяти ввода-вывода зарезервировано три ячейки: одна под регистр данных – PORTx, другая под регистр направления данных – DDRx и третья под состояние входов порта – PINx. Ячейка, хранящая состояние на входах портов, доступна только для чтения, а регистры данных и направления данных имеют двунаправленный доступ. Кроме того, установка бита выключения подтягивающих резисторов PUD регистра SFIOR отключает функцию подтягивания на всех выводах всех портов.

Ниже приведено описание порта ввода-вывода для универсального цифрового ввода-вывода. Большинство выводов портов поддерживают альтернативные функции встроенных периферийных устройств микроконтроллера. Описание альтернативных функций приведено далее в подразделе “Альтернативные функции порта” (см. также описание функций соответствующих периферийных модулей).

Обратите внимание, что для некоторых портов разрешение альтернативных функций некоторых выводов делает невозможным использование других выводов для универсального цифрового ввода-вывода.

Порты в качестве универсального цифрового ввода-вывода

Все порты являются двунаправленными портами ввода-вывода с опциональными подтягивающими резисторами. Рисунок 30 иллюстрирует функциональную схему одной линии порта ввода-вывода, обозначенный как Pxn.


Рисунок 30 – Организация универсального цифрового ввода-вывода (1)

Прим. 1: Сигналы WPx, WDx, RRx, RPx и RDx являются общими в пределах одного порта. Сигналы clkI/O, SLEEP, и PUD являются общими для всех портов.

Настройка выводов

Режим и состояние для каждого вывода определяется значением соответствующих разрядов трех регистров: DDxn, PORTxn и PINxn. Как показано в “Описании регистров портов ввода-вывода” доступ к битам DDxn возможен по адресу DDRx в пространстве ввода-вывода и, соответственно, к битам PORTxn по адресу PORTx, а к битам PINxn по адресу PINx.

Биты DDxn регистра DDRx определяют направленность линии ввода-вывода. Если DDxn = 1, то Pxn конфигурируется на вывод. Если DDxn=0, то Pxn конфигурируется на ввод.

Если PORTxn = 1 при конфигурации линии порта на ввод, то разрешается подключение подтягивающего резистора. Для выключения данного резистора необходимо записать в PORTxn лог. 0 или настроить линию порта на вывод. Во время сброса все линии портов находятся в третьем (высокоимпедансном) состоянии, даже если не работает синхронизация.

Если PORTxn = 1 при конфигурации линии порта на вывод, то состояние выхода будет определяться значением PORTxn.

Поскольку одновременная запись в регистры DDRx и PORTx невозможна, то при переключении между третьим состоянием ({DDxn, PORTxn} = 0b00) и выводом лог. 1 ({DDxn, PORTxn} = 0b11) должно возникнуть промежуточное состояние или с подключенным подтягивающим резистором ({DDxn, PORTxn} = 0b01) или с выводом лог. 0 ({DDxn, PORTxn} = 0b10). Как правило, переход через состояние с подключением подтягивающего резистора эквивалентно состоянию вывода лог.1, если вывод микроконтроллера связан с высокоимпедансным входом. В противном случае, необходимо установить бит PUD регистра SFIOR для выключения всех подтягивающих резисторов на всех портах

Переключение между вводом с подтягивающими резисторами и выводом низкого уровня связано с аналогичной проблемой. Поэтому, пользователь вынужден использовать или третье состояние ({DDxn, PORTxn} = 0b00) или вывод лог. 1 ({DDxn, PORTxn} = 0b11) в качестве промежуточного шага.

В таблице 25 подытоживается действие управляющих сигналов на состояние вывода.

Таблица 25 – Настройка вывода порта

DDxn PORTxn PUD (в SFIOR) Ввод-вывод Подтягивающий резистор Комментарий
0 0 X Ввод Нет
0 1 0 Ввод Да Pxn будет источником тока при подаче внешнего низкого уровня
0 1 1 Ввод Нет Третье состояние (Z-состояние)
1 0 X Вывод Нет Вывод лог. 0 (втекающий ток)
1 1 X Вывод Нет Вывод лог. 1 (вытекающий ток)

Считывание состояние вывода

Независимо от значения бита направления данных DDxn состояние вывода порта может быть опрошено через регистровый бит PINxn. Как показано на рисунке 30 регистровый бит PINxn и предшествующая ему триггерная защелка составляют синхронизатор. Данный подход позволяет избежать метастабильности, если изменение состояния на выводе произошло около фронта внутренней синхронизации. Однако такой подход связан с возникновением задержки. На рисунке 31 представлена временная диаграмма синхронизации во время опроса внешне приложенного к выводу уровня. Длительности минимальной и максимальной задержек на распространение сигнала обозначены как tpd,max и tpd,min, соответственно.


Рисунок 31 – Синхронизация во время опроса приложенного к выводу порта уровня

В следующих примерах показано как установить на линиях 0 и 1 порта В уровень лог. 1, а на линиях 2 и 3 – лог. 0, а также как настроить линии 4…7 на ввод с подключением подтягивающих резисторов на линиях 6 и 7. Результирующее состояние линий считываются обратно, но, с учетом сказанного выше, включена инструкция nop для обеспечения возможности обратного считывания только что назначенного состояния некоторых выводов.

Пример кода на Ассемблере (1) ... ; Разрешаем подтягивание и устанавливаем высокие выходные уровни; Определяем направления данных линий портов ldi r16,(1<

Прим. 1: В программе на Ассемблере используются два временных регистра для минимизации интервала времени от настройки подтягивающих резисторов на разрядах 0, 1, 6 и 7 до корректной установки бит направления, разрешающих вывод лог. 0 на линиях 2 и 3 и заменяющих высокий уровень на разрядах 0 и 1, образованный за счет подключения подтягивающих резисторов, на высокий уровень сильноточного драйвера.

Разрешение цифрового ввода и режимы сна

Как показано на рисунке 30 входной цифровой сигнал может быть зашунтирован к общему на входе триггера Шмита. Сигнал, обозначенный на рисунке как SLEEP, устанавливается при переводе микроконтроллера в режим выключения (Power-down), экономичный режим, дежурный режим и расширенный дежурный режим. Это позволяет избежать повышения потребляемого тока в случае, если некоторые входные сигналы окажутся в плавающем состоянии или уровень входного аналогового сигнала будет близок к VCC/2.

Сигнал SLEEP игнорируется по входам внешних прерываний. Если запросы на внешнее прерывание отключены, то SLEEP действует и на эти выводы. SLEEP также игнорируется на некоторых других входах при выполнении их альтернативных функций (см. “Альтернативные функции порта ”).

Если на выводе внешнего асинхронного прерывания, настроенный на прерывание по нарастающему фронту, падающему фронту или на любое изменение, присутствует уровень лог. 1 и при этом внешнее прерывание не разрешено, то соответствующий флаг внешнего прерывания будет установлен при выходе из выше упомянутых режимов сна, т.к. функция шунтирования на входе в режимах сна приводит возникновению логических изменений.

Неподключенные выводы

Если несколько выводов остаются неиспользованными, то рекомендуется гарантировать на них присутствие определенного логического уровня. Не смотря на то, что большинство цифровых входов отключены в режимах глубокого сна, как описано выше, необходимо избежать наличия плавающих входов во избежание повышенного потребления тока во всех других режимах работы микроконтроллера, где цифровой ввод разрешен (Сброс, Активный режим и режим холостого).

Самым простым методом гарантирования присутствия определенного уровня на неиспользуемом выводе является разрешение подключения внутреннего подтягивающего резистора. Однако в этом случае в режиме сброса подтягивающие резисторы будут отключены. Если требуется малое потребление и в режиме сброса, то необходимо устанавливать внешний подтягивающий резистор к плюсу или к минусу питания. Подключение выводов непосредственно к VCC или GND не рекомендуется, т.к. может возникнуть опасный ток при случайной конфигурации такого вывода на вывод данных.

Долгое время мы оставляли без внимания микроконтроллеры AVR , и вот пришла пора исправить это недоразумение! Как и для других контроллеров, будем постепенно рассматривать различную периферию AVR’ок, сначала теорию, всякие регистры, ну и под конец небольшие примерчики.

В качестве IDE я использую AVR Studio 5 , шестую версию AVR Studio даже не пробовал, не так часто последнее время мне попадаются задачи для AVR) А вообще неплохо иметь еще и установленную AVR Studio 4, потому что порой случается так, что запрограммировать контроллер из AVR Studio 5 не представляется возможным. Вот совсем недавно я хотел прошить ATMega2560 при помощи программатора STK500 и это оказалось неосуществимо через 5 студию) Хорошо осталась со старых времен AVR Studio 4, и проблема решилась в течение пары минут.

Что тут еще можно сказать?.. Да в принципе, это все, можно переходить к делу;)

Начинать, естественно, будем с GPIO – портов ввода-вывода , потому как без них никуда) И прежде чем описывать регистры, которые управляют работой портов, отмечу несколько «электрических» моментов.

На входе каждой ножки микроконтроллера заботливые разработчики поставили пару диодов, которые должны спасти микроконтроллер в случае превышения допустимого напряжения. НО! На деле все не столь радужно, и если подать на вход микроконтроллера, например, 7.5 Вольт, то контроллеру уже никто и ничто не поможет, проверено на собственном опыте. Поэтому все эксперименты нужно проводить аккуратно)

Теперь к регистрам. Вся работа с портами ввода-вывода в AVR’ках сосредоточена в трех регистрах – DDRx, PORTx, PINx. Символ «x» заменяется соответствующим названием порта (A,B…). То есть, если мы хотим работать с портом A микроконтроллера, то нам нужны регистры DDRA, PORTA, PINA. Если мы хотим работать с пятым пином порта А (РА5), то нас интересует пятый бит упомянутых выше регистров. Как видите, все довольно просто) Осталось лишь разобраться, что же и куда надо записывать, за что отвечает каждый из этих регистров. Итак, начали…

Регистр DDRx.

DDRx отвечает за направление работы соответствующих выводов микроконтроллера. Каждый пин может быть либо входом, либо выходом, третьего не дано. Для того, чтобы настроить вывод на работу в режиме входа в регистр DDR для этого порта нужно записать 0, для выхода – 1. Пусть нам нужно настроить РА6 как вход, а РА3 как выход. Что делаем? Верно, третий бит регистра DDRA выставляем в 1, а в 6 бит все того же регистра DDRA записываем 0. Готово!

Регистр PINx.

В этот регистр мы записать ничего не можем, он предназначен исключительно для чтения данных. В этом регистре информация об уровне сигнала на соответствующем порте. Как мы помним, микроконтроллер – это цифровое устройство, и сигналы на его ножках могут иметь либо высокий уровень (логическая 1), либо низкий (логический 0). Если мы хотим узнать, что у нас там на входе РВ4, то нас интересует четвертый бит регистра PINB.

Регистр PORTx.

С этим регистром чуть посложнее, чем с предыдущими. Его функциональность зависит от того, в какой направлении работают выводы микроконтроллера. Если вывод используется в качестве входа, то регистр PORTx задает тип входа. Тут возможно два варианта:

PORTx = 1 – при такой конфигурации мы получаем вход с подтяжкой вверх (PullUp)

PORTX = 0 – высокоимпедансный вход (Hi-Z) – это значит, что сопротивление порта настолько велико, что его можно считать бесконечным)

Итак, продолжаем с регистром PORTx. Если вывод работает в качестве выхода, а в регистре PORTx единица – то на выводе будет высокий уровень сигнала, аналогично, PORTx = 0 – низкий уровень.

Давайте небольшой пример для иллюстрации 😉

Настроим вывод РС4 на работу в режиме входа с подтяжкой вверх. Для этого в четвертый бит регистра DDRC запишем 0 (режим входа), а в регистре PORTC четвертый бит надо выставить в 1 (подтяжка вверх). Вот и все.

В принципе это все, что касается теории, углубляться дальше не будем. Осталось соблюсти традиции и поиграть с диодиком. Пусть диод подключен к выводу PВ5. Заставим его помигать! И прежде всего создадим проект. Я, как уже говорил, использую AVR Studio 5, а в качестве контроллера выберу мой любимый ATMega88)

// Подключаем нужный файл #include /*******************************************************************/ // Простенькая функция для формирования задержки void delay(unsigned int time ) { unsigned int i = 0 ; for (i = 0 ; i < time ; i++ ) ; } /*******************************************************************/ // Инициализация нашего вывода, он работает на выход void initAll() { DDRB = 0b00100000 ; } /*******************************************************************/ // Собственно тело функции main() int main(void ) { initAll() ; while (1 ) { // Зажигаем диод PORTB = 0b00100000 ; // Отдыхаем delay(50000 ) ; // Гасим диод PORTB = 0b00000000 ; delay(50000 ) ; } } /*******************************************************************/

Вот таким получилось наше первое общение с микроконтроллерами AVR . В скором времени рассмотрим по возможности всю их периферию, а потом можно замутить что-нибудь поинтересней 😉

  • Сергей Савенков

    какой то “куцый” обзор… как будто спешили куда то