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

Работа с регистрами AVR микроконтроллера на Си, битовые операции

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

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

Содержание:

  1. Структура байта
  2. Порты, байты и биты
  3. Операции битового сдвига
  4. Битовые операторы в языке Си
  5. Установка битов в регистре порта
  6. Сброс битов в регистре порта
  7. Проверка разрядов регистра
  8. Инверсия состояния бита в регистре
  9. Заключение

Структура байта

Мы знаем что один байт представляет собой 8 бит, а каждый бит - это 1 или 0. Биты в байте считаются справа-налево. Бит 1 является младшим, а бит 8 - старшим.

1 Байт
8 (старший бит) 7 6 5 4 3 2 1 (младший бит)

Порты, байты и биты

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

В языке Си для AVR,  установка значения 1 для бита в порте - это как перевод нужного переключателя в состояние "включено". На выходе канала для указанного порта, к которому подвязан наш виртуальный выключатель, появится высокий уровень, а это в свою очередь подаст напряжение на какое-то устройство, например на светодиод, который сразу же засветится.

Названия каналов в порте микроконтроллера отсчитываются с нуля (0). Ниже приведен пример битовой структуры порта PORTD:

  Регистр порта PORTD, 1 байт
Номер бита в регистре 8 7 6 5 4 3 2 1
Канал порта PD7 PD6 PD5 PD4 PD3 PD2 PD1 PD0

Аналогично по структуре выглядят и другие порты - PORTA, PORTB, DDRD, PINA...

Предустановленные значения констант PD0, PD1, PB5, PA4 (и многих других) для каждого типа AVR-микроконтроллера указаны в специальном заголовочном файле библиотеки avr-libc.

Например, чтобы вывести на экран значения всех констант из IO-файла для микроконтроллера ATmega8, содержащих сочетание символов "PD" (ищем константы для порта D, PORTD), достаточно выполнить следующую команду:

cat /usr/lib/avr/include/avr/iom8.h | grep PD

Увидим следующий результат:

#define SPDR    _SFR_IO8(0x0F)
#define PD7      7
#define PD6      6
#define PD5      5
#define PD4      4
#define PD3      3
#define PD2      2
#define PD1      1
#define PD0      0

Теперь, при использовании константы PD0 в своей программе на Си, вы знаете что в ней содержится число 0, а в PD3 - 3 и т.д.

Операции битового сдвига

А сейчас, давайте подробно и с примерами разберемся с тем, как работают операторы битового сдвига.

Существует несколько разновидностей операций битового сдвига:

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

Операторы битового сдвига в языке программирования Си обозначаются как ">>" и "<<" и выполняют логический сдвиг битов в используемой переменной в указанном направлении и на указанное число элементов.

Важный нюанс: при сдвиге вправо (">>") битов в числе с отрицательным знаком (signed) выполняется арифметический сдвиг - освободившиеся позиции слева заполняются единичками (перенос знака). Это важно помнить!

Сокращенные обозначения:

  • Bin - от слова Binary, двичная система счисления;
  • Dec - от слова Decimal, десятичная система счисления;
  • Hex - от слова Hexadecimal, шестнадцатиричная система счисления.

Для примера выполним сдвиги битов в разных числах, предварительно представив их в двоичном виде.

Для числа 1 (Dec, в десятичной системе 1) - 00000001 (Bin, в двоичной системе 0b00000001):

  • 1 << 0 = 1 (00000001);
  • 1 << 1 = 2 (00000010);
  • 1 << 2 = 4 (00000100)
  • 1 << 5 = 32 (00100000);
  • 1 >> 2 = 0 (00000000).

Сдвиг влево на один разряд выполняет умножение числа на 2, а сдвиг вправо - деление числа на 2.

Для числа 209 (Dec, в десятичной системе 209) - 11010001 (Bin, в двоичной системе 0b11010001):

  • 209 = 11010001;
  • 209 << 3 = 136 (10001000);
  • 209 << 5 = 32 (00100000);
  • 209 >> 5 = 6 (00000110)

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

Битовые операторы в языке Си

То как двигать биты в байте мы теперь знаем, дальше разберемся с битовыми операторами в Си:

  • "&" (логическое И, AND) или умножение - бинарная операция, результат которой равен 1 только в том случае если оба операнда равны 1, в противном случае будем иметь 0;
  • "|" (логическое ИЛИ, OR) или сложение - бинарная операция, результат которой равен 1 в том случае если хотя бы один из операндов равен 1;
  • "~" (логическое НЕ) или инверсия - унарная операция, результат которой равен 0 если операнд равен 1, и наоборот - результат равен 1, если операнд равен 0;
  • "^" (исключающее ИЛИ, XOR) - бинарная операция, результат которой равен 1 в том случае если только один из двух операндов равен 1.

Рассмотрим примеры битовых операций над числами 209, 7 и их битовыми представлениями:

1101 0001 (209)
&

0000 0111 (7)
----------------
0000 0001 (1)

1101 0001 (209)
|
0000 0111 (7)
----------------
1101 0111 (215)
1101 0001(209)
~
----------------
0010 1110(46)
1101 0001 (209)
^
0000 0111 (7)
----------------
1101 0110 (214)

Как видите, битовые операции позволяют установить или сбросить отдельные биты числа.

Установка битов в регистре порта

А теперь немного практики, давайте сделаем установку 6-го бита в регистре порта PORTB что в свою очередь установит высокий уровень для канала PB5 (6-й бит в регистре).

Допустим что сейчас в регистре PORTB содержится число 136, которое в битовом представлении выглядит как 10001000 (высокий уровень на каналах PB7 и PB3).

Чтобы установить 6-й бит (10001000) мы будем использовать битовую операцию логического ИЛИ в комплексе с битовой маской.

Что такое битовая маска? - по сути это специально подготовленное число, состоящее из требуемой конфигурации битов, которое в сочетании с некоторой операцией над битами другого числа позволяет установить или сбросить биты в последнем.

Для получения битовой маски, при помощи которой позже будет установлен один бит, мы выполним левосторонний сдвиг битов числа 1 (00000001) на 5 разрядов:

0000 0001 (1)
<< 5
----------------
0010 0000 (32)

В результате битовой операции получим число 32 (00100000), это и есть наша битовая маска. Хочу заметить что это число равняется числу 2 в 5-й степени, каждый сдвиг разряда влево умножает результат на 2.

Теперь нам останется выполнить битовую операцию ИЛИ над текущим числом в регистре и получившимся числом-маской:

1000 1000 (136)
|
0010 0000 (32)
----------------
1010 1000 (168)

А теперь сравните состояние регистра перед операцией и после - все состояния битов сохранены и дополнительно установлен 6-й бит.

Для установки 6-го бита и последующей записи числа в регистр порта PORTB (установка высокого уровня для канала PB5) в нашем примере можно использовать любую из следующих конструкций операторов, они все выполняют идентичную задачу:

  • PORTB = PORTB | 32;
  • PORTB = PORTB | (1 << 5);
  • PORTB = PORTB | (1 << PB5);
  • PORTB |= (1 << PB5);

Наиболее удобно использовать последнюю краткую запись, где используется комбинирования операция логического ИЛИ и присвоения, в данном случае PB5.

К примеру константа PB5 (канал 5 порта B, 6-й бит регистра) определена в файле /usr/lib/avr/include/avr/iom8.h для микроконтроллера ATmega8 и она равна числу 5.

Как установить несколько бит в регистре? - можно вызвать поочередно две конструкции с операторами, а можно все выполнить одной командой. Допустим нужно установить 2-й и 6-й биты в регистре порта PORTD, что соответствуют каналам PD1 и PD5:

  • PORTD |= ( 1 << 1 ) | ( 1 << 5 );
  • PORTD |= ( 1 << PD1 ) | ( 1 << PD5 );

Сброс битов в регистре порта

Для сброса разрядов в регистре порта мы будем использовать битовую операцию "&" (логическое "И"), которая применяется к двум битам (бинарная операция) и даёт единицу только в том случае если оба исходных бита имеют единичное значение, также нам пригодится битовая операция "~" (логическое "НЕ", инверсия).

Давайте выполним сброс 5-го бита в регистре порта PORTD, что в свою очередь выполнит установку низкого уровня на канале PD4. Допустим что сейчас в регистре PORTD содержится число 157, которое в битовом представлении выглядит как 10011101.

Для того чтобы сбросить 5-й бит (10011101) в регистре порта PORTD мы подготовим маску (как при установке битов), инвертируем ее биты "~", а потом выполним битовую операцию "&" над текущим значением регистра и полученной инвертированной маской.

Для подготовки маски выполним сдвиг битов на 4 разрядов в числе 1 (00000001).

0000 0001 (1)
<< 4
----------------
0001 0000 (16)

Маска готова, получили число 16 (00010000), 2 в 4-й степени. Выполним инверсию битов:

0001 0000 (16)
~
----------------
1110 1111 (239)

Готово, осталось применить маску к содержимому регистра порта PORTB используя битовую операцию "&":

1001 1101 (157)
&
1110 1111 (239)
----------------
1000 1101 (141)

Теперь в содержимом регистра PORTD значение 5-го бита установлено в 0 с сохранением положений остальных бит. В языке Си данные операции можно выполнить используя любую из приведенных ниже, идентичных по результату команд:

  • PORTD = PORTD & ~ 16;
  • PORTD = PORTD & 239;
  • PORTD = PORTD & ~( 1 << 4 );
  • PORTD = PORTD & ~( 1 << PD4 );
  • PORTD &= ~( 1 << PD4 );

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

  • PORTD = PORTD & ~( ( 1 << PD4 ) | ( 1 << PD6 ) );
  • PORTD &= ~( ( 1 << PD4 ) | ( 1 << PD6 ) );

Проверка разрядов регистра

Теперь разберемся каким образом можно проверить разряды регистра на наличие в них 1 или 0. Это может потребоваться если нужно получить значение битов в регистрах специального назначения (флагов) микропроцессора, а также для чтения состояния различных устройств и модулей, которые передают свое состояние используя битовую структуру.

Как проверить значение установленного бита в регистре на Си? - для этого нужно подобрать специальное выражение с использованием битовых операций, результатом работы которого будет значение: правда (True) или ложь (False). Имея булево (bool) значение выражения мы можем использовать для работы условные операторы языка Си.

Например нам нужно проверить есть ли единица (1) в 3-м бите регистра PORTD, тем самым мы проверим есть ли высокий уровень на канале PD2 порта PORTD. Примем что текущее значение регистра - 10010101 (149).

Для проверки используем выражение, которое состоит из битовой маски с установленным битом для проверки и проверяемого регистра, к которым применен битовый оператор "&" (логическое И).

Готовым маску, в которой только 3-й бит установлен в 1. Для этого выполним сдвиг битов числа 1 на 2 разряда влево:

0000 0001 (1)
<< 2
----------------
0000 0100 (4)

Теперь применим битовую операцию "&" (логическое И) к содержимому регистра PORTD и получившейся маске:

1001 0101 (149)
&
0000 0100 (4)
----------------
0000 0100 (4)

В результате выражения получим число 4 (0000 0100).

В языке Си все числа которые НЕ равны "нулю" (-100, -5, 1, 500) являются логической истиной (True), а 0 - логической ложью (False).

Результат нашего выражения - число 4, которое является логической истиной (True), а это значит что 3-й разряд регистра PORTD содержит единицу.

Вот как будет выглядеть данное выражение из двух логических операций на языке Си:

PORTD & (1 << 2)

Такое выражение можно использовать в условных операторах (if) и операторах циклов (while), например:

while( PORTD & (1 << 2) ) { ... }
if( PORTD & (1 << PD2) ) { ... }

Для проверки состояния бита в регистре на ноль (0) используем такую же конструкцию, только к результату выражения применим логическую операцию инверсии "!" (логическое НЕ).

Логическая операция "!" переворачивает значение с правды (True) на ложь (False), и наоборот. В отличие от битовой операции инверсии, которая переворачивает биты с 1 на 0 и наоборот, логическая операция инверсии оперирует с логическими значениями: правда (True) на ложь (False).

1 = True  0 = False 122 = True  (149 & (1 << 2)) = True
!1 = False  !0 = True !(5-1) = False  !(149 & (1 << 2)) = False

Пример выражения на языке Си для проверки на ноль (0) 3-го бита в регистре порта PORTD (канал PD2):

while( !(PORTD & (1 << 2)) ) { ... } 
if( !(PORTD & (1 << PD2)) ) { ... }

Здесь выражение "PORTD & (1 << 2)" берется в круглые дужки, что позволяет получить результат этого выражения, к которому потом и будет применен оператор логической инверсии.

Инверсия состояния бита в регистре

Иногда может понадобиться изменить состояние определенного бита в регистре на противоположное - выполнить инверсию состояния бита.

Для подобной операции отлично подходит битовый оператор "^" (исключающее ИЛИ, XOR). Чтобы выполнить инверсию определенного бита в регистре нужно создать маску, в которой этот бит установлен, а потом применить к содержимому регистра и полученной маске бинарный оператор "^", потом останется записать полученный результат в регистр и готово.

Возьмем, к примеру, что нужно погасить светодиод, который подключен к каналу PD5 порта PORTD. Если светодиод светится то это значит что в на канале PD5 присутствует высокий уровень, соответственно это значит что в регистре порта PORTD бит под номером 6 (PD5 = 5, 6-й бит в байте регистра) установлен в 1. Допустим что содержимое регистра порта PORTD сейчас - 10111010 (число 186, 1 байт, 8 разрядов, 6-й разряд = 1).

Подготовим маску, для установки 6-го бита нам необходимо сдвинуть все биты числа 1 на 5 разрядов:

0000 0001 (1)
<< 5
----------------
0010 0000 (32)

Применим маску к содержимому регистра порта PORTD:

1011 1010 (186)
^
0010 0000 (32)
----------------
1001 1010 (154)

Как видите, 6-й бит в байте регистра, который раньше был 1, сейчас установлен в 0 (1001 1010). Теперь осталось записать число в регистр порта и задачу можно считать выполненной.

Примеры использования такой конструкции на языке Си:

  • PORTD = PORTD ^ 32;
  • PORTD = PORTD ^ (1<< 5);
  • PORTD = PORTD ^ (1<< PD5);
  • PORTD ^=(1<< PD5);

Как и в предыдущих примерах по установке и сбросу битов, последняя конструкция (я ее выделил) является наиболее краткой и понятной для использования.

Заключение

С первого взгляда выражения наподобие "&", "!", "PD1", ">>" с их комбинациями могут показаться очень сложными. Но если собрать все терпение с вниманием и один раз хорошенько разобраться, попробовав примеры на практике, то в будущем будет уже не сложно понять как работают такие конструкции в коде программы и какой результат от них ожидать.

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

Начало цикла статей: Программирование AVR микроконтроллеров в Linux на языках Asembler и C.

Комментарии к публикации (30):
папа #1папа
31 Октябрь 2016 20:28

Как установить несколько бит в регистре? - можно вызвать поочередно две конструкции с операторами, а можно все выполнить одной командой. Допустим нужно установить 2-й и 6-й биты в регистре порта PORTD, что соответствуют каналам PD1 и PD5:

PORTB |= ( 1 << 2 ) | ( 1 << 6 );

+3
Дмитрий #2Дмитрий
16 Февраль 2017 10:57

В таблице раздела Битовые операторы в языке Си в последнем столбце ошибочка, или как?

+1
ph0en1x #3ph0en1x
16 Февраль 2017 16:02

Дмитрий, спасибо за внимательность. Ошибка исправлена.

+1
ph0en1x #4ph0en1x
27 Февраль 2017 14:26

Недавно получил такое письмо:

Прочёл вашу статью. Но на других сайтах встретил такие записи:
MCUCR=(0<<ISC00)|(1<<ISC01);
DDRB |= ((1<<PB2)|(1<<PB0));                  
TCCR0A=(1<<COM0A1) | (1<<WGM00);
 SREG|= (1<<7);
DDRD  = (0 << PD2);
Так всё таки ноль можно писать? И чем отличается выражение имеющий
знак "|" от выражения не имеющего знак "|"?

Знак "|" - это логическая операция ИЛИ (OR).
К примеру, записи ниже эквивалентны между собою:
"SREG |= (1<<7);" - то же самое что и "SREG = SREG | (1<<7);"
Допустим что SREG содержит значение 01110001, вот что получится:
1 = 00000001 (в двоичном представлении)
00000001 << 7 = 10000000 (число 128)
01110001 | 10000000 = 11110001
Таким образом, мы выполнили установку восьмого(старшего) бита в регистре SREG (регистр состояния микроконтроллера) чем разрешили использование перерываний в МК.

Теперь рассмотрим вот эту запись:
DDRD  = (0 << PD2);
Здесь выполняется логический сдвиг числа 0 влево на 2 позиции (PD2=2, смотрим значение для своего МК).
00000000 << 2 = 00000000
Приведенную выше запись можно использовать для наглядности, хотя по сути она эквивалентна:
DDRD  = 0

Следующая конструкция нужна для установки бит ISCxx в регистре MCUCR (настройка прерываний):
MCUCR = (0<<ISC00) | (1<<ISC01);
Она эквивалентна вот этим конструкциям:
MCUCR = 0 | (1<<ISC01);
MCUCR = (1<<ISC01);
В первом варианте выражение "0<<ISC00" используется для наглядности, так более понятно что и как установлено в регистре.

0
VB99 #5VB99
04 Март 2017 09:40

Хочу выразить Вам огромную благодарность!

Как для новичка, то все очень доступно и понятно изложено.

Спасибо за Ваш труд!

+8
Ivan #6Ivan
14 Март 2017 19:06

«В языке Си все числа что равны или больше 1 являются логической истиной (True), а 0 - логической ложью (False).»
Исправьте , так как в Си, все числа что не равны 0 (-1,-5,1,5,150…) являются логической истиной (True), а 0 — логической ложью (False)

+1
ph0en1x #7ph0en1x
14 Март 2017 22:28

Ivan, благодарю за хорошее замечание! Исправил.

+1
Schneider #8Schneider
04 Май 2017 18:27

Спасибо,
Переехал с другого языка и никак не мог понять, что же там внутри происходит, что значат такие записи. Истолковано все отлично. Про битовые сдвиги инфы достаточно. Но здесь истолкована сама логика записи. Что важно для запоминания такой нотации. Третий день вникаю. Помогли! Спасибо!

+4
shabmen #9shabmen
04 Сентябрь 2017 11:45

Не очень понял:
"Операторы битового сдвига ">>" и "<<" в языке программирования Си выполняют сдвиг битов в переменной вправо и влево на указанное число элементов. Биты которые были сдвинуты теряются, а с другой стороны появляются нули - выполняется логический сдвиг."
если для вышеприведенного примера
1000 1000 (136)
осуществить сдвиг первого разряда 5 раз, то должны получить
0000 0000 - т.к. двигается ноль, а все, что за ним слева "затирает" в нули.
Если мы 1<<5 сначала записываем первый бит единицу , а потом ее двигаем, то в чем смысл, ведь в первый (PB0) нам надо сначала каким то образом записать единицу? Поэтому как я понимаю работу сдвигового регистра сдвигается значение разряда, а не желаемое значение разряда. Ведь запись 1<

0
ph0en1x #10ph0en1x
05 Сентябрь 2017 21:19

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

1000 1000 (136)
1000 1000 << 5 = 1 0001 0000 0000 (4352) - биты сдвинулись влево, справа появились нули, такое число можно записать в 16-биный регистр (2 байта по 8 бит).

Если же это число попробовать записать в 8-ми битный регистр МК то там окажется число 0000 0000 (0). Для демонстрации сделал небольшую анимацию работы программы в симуляторе AVRStudio, где в регистр микроконтроллера помещаем число 1000 1000 сдвинутое на 5 разрядов влево:

Симуляция в AVRStudio - потеря 8 бит 16-битного числа при записи в 8-битный регистр микроконтроллера

0
d9v4 #11d9v4
24 Январь 2018 22:02

000'1' 0000 (16)

Маска готова, получили число 16 (00010000), 2 в 4-й степени. Выполним инверсию битов:

00'1'0 0000 (16)

опечаточка))

0
ph0en1x #12ph0en1x
26 Январь 2018 17:41

d9v4, спасибо! Исправлено.

+2
Magnetic #13Magnetic
18 Март 2018 22:44

В разделе "Проверка разрядов регистра" в предложении "Готовым маску, в которой только 3-й бит установлен в 0. Для этого выполним сдвиг числа 1 на 2 разряда:", похоже опечатка, т.к. 3-й бит маски устанавливается не нулем а единицей.

0
ph0en1x #14ph0en1x
19 Март 2018 00:39

Magnetic, спасибо Вам! Опечатка исправлена.

0
Denis #15Denis
27 Март 2018 06:49

Сложновато. Я в CVAVR привык к записи типа PORTB.2=1; А все записи имеющие ^,!,>>,&&,| и т д с ума сводят.

+1
ДмитрийА #16ДмитрийА
09 Декабрь 2018 05:51

Спасибо, все очень понятно, с примерами и без нагромождений. Вопрос: если маску вместо скажем выражения сдвига 1<<5 сразу писать 32 , то при выполнении программы это займет столько же времени(тактов) контроллера, или меньше? А при записи изменения состояния нескольких битов регистра одной строкой, они меняются за один такт или по очереди?

0
ph0en1x #17ph0en1x
10 Декабрь 2018 17:05

Конструкция "1<<5" при компиляции будет преобразована в число 32, поэтому после прошивки количество требуемых тактов процессора не изменится.

При использовании цепочки из нескольких конструкций для выполнения потребуется дополнительный такт микропроцессора (МП).

Можете проверить это используя пошаговое выполнение в AVR Studio, интересный пример с анимацией я приводил в одном из комментариев к статье Простая программа для AVR микроконтроллера на языке Си.

#include <avr/io.h> 

void main(void) { 
PORTD |= 32;         // 2 такта МП
PORTD |= (1<<PD0);   // 2 такта МП
PORTD |= (1<<PD3) | (1<<PD5);   // 3 такта МП
PORTD |= (1<<PD3) | (1<<PD4) | (1<<PD5) | (1<<PD6);  // 3 такта МП
}
+1
Flagmans #18Flagmans
20 Декабрь 2018 19:22

Не понимаю, куда вводить:
cat /usr/lib/avr/include/avr/iom8.h | grep PD

0
ph0en1x #19ph0en1x
21 Декабрь 2018 17:07

Flagmans, эта команда из примера выполняется в консоли операционной системы GNU/Linux и выводит на экран дополнительную полезную информацию. Все остальные примеры из статьи относится к языку программирования Си и не привязаны к какой-то конкретной платформе.

+2
Dimon0014 #20Dimon0014
26 Январь 2019 09:52

Отличная статья! Все очень подробно и самое главное на примере AVR. Я думал буду с этим долго разбираться, но тут прям все так залилось на раз и два. Спасибо!
P/S
Всегда думал:"Нафиг нужны эти битовые операторы в Си?" И вот [s]докатился[/s] дожил до их примененияLol.
Прикольно так же что 1 это ж литерал, а при помощи битовых операций мы ее изменяем.

+3
Mircucip #21Mircucip
22 Февраль 2019 19:01

Присоединюсь к лайкам. Мне тоже информация легла очень ровно и понятно. Давно ждал именно такого объяснения, чтоб все срослось в одну картину.
Спасибо!

+2
Артём #22Артём
23 Февраль 2019 20:27

Как и многие делаю первые шаги в изучении микроконтроллеров AVR по книге Белова А.В. Примеры на Си там приведены, написанные в платной CodeVisionAVR. Долго не мог понять, почему они у меня не работают в Atmel Studio. Оказалось, что обращение к отдельным разрядам наподобие PORT5.2, как написано в книге, не работает в Atmel Studio, вот тут и помогла ваша статья.
В общем, ph0en1x, спасибо!
(Сайтик в закладки)

+3
Santehnik #23Santehnik
17 Март 2019 18:09

Здравствуйте, есть код для atmega323 и подобных:
void twi_Start(void){
TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);   //TWSTA - флаг состояния СТАРТ.
while(!(TWCR & (1<<TWINT))){};   //проверяет бит TWINT на ноль, а зачем???
}
Зачем там проверка бита TWINT регистра TWCR на ноль, если его только что в 1 установили?

0
ph0en1x #24ph0en1x
18 Март 2019 12:45

Здравствуйте. С интерфейсом TWI в AVR пока что не работал, но несколько минут изучения протокола дало ответ на ваш вопрос.

В строчке "TWCR=..." выполняется сброс флага прерывания TWINT и включение модуля TWI (Two Wires Interface).

Строка "while..." содержит цикл ожидания готовности МК к передаче данных (МК выдаст на шину состояние START, флаг TWINT будет аппаратно установлен) после включения шины. Как только МК аппаратно установит флаг TWINT и будет готов к передачи данных, вечный цикл с проверкой бита TWINT завершится и МК перейдет к выполнению других операций.

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

0
Cadet #25Cadet
11 Апрель 2019 05:37

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

И еще, по поводу проверки битов. По мне так проще использовать регистр PINx для проверки разрядов регистра, для единицы (1) это будет ~PINB & (1<<3). Тем более это регистр для чтения, и при ошибке ничего никуда не запишется. Хотя, каждый ..., как говорится, как хочет)

+2
Виктор #26Виктор
24 Сентябрь 2019 14:36

Подскажите пожалуйста, в секции (Установка битов в регистре порта) условие (Допустим нужно установить 2-й и 6-й биты в регистре порта PORTD) в примере:
PORTB |= ( 1 << 1 ) | ( 1 << 5 );
PORTB |= ( 1 << PD1 ) | ( 1 << PD5 );
Правильно я понимаю, что вместо PORTB должно быть PORTD?
Я дико извиняюсь, только только учусь и каждая запись для меня очень важна!

+1
ph0en1x #27ph0en1x
24 Сентябрь 2019 16:41

Здравствуйте, Виктор. Верно подметили, в статье была опечатка.
Исправил PORTB на PORTD для последнего примера в разделе "Установка битов в регистре порта".
PORTD - порт под названием D, а PD1 - это один из выходов (Pin) порта D под номером 1. С другими константами портов и пинов - аналогично.
На практике конструкция "PORTB |= ( 1 << PD1 ) | ( 1 << PD5 );" с ошибочной записью все же будет работать, поскольку значения констант в заголовочных (.h) файлах для AVR микроконтроллеров равны по значениям: PD1=PB1, PD2=PB2..PD7=PB7.

0
Миша #28Миша
25 Июль 2020 00:43

Автор =топ_1
Наконец-то я понял что к чему!!

Yahoo

+1
felix #29felix
12 Декабрь 2020 19:06

Приветствую. Надо избавиться от digitalRead , заменяю на ~PINx&(1<<xx), но код работает не корректно (энкодер на пинах A2, A3 mega328). Может кто подсказать в чем причина?

// алгоритм с "таблицей", позволяющий увеличить точность энкодера
// в 4 раза, работает максимально чётко даже с плохими энкодерами.
// Для увеличения скорости опроса используйте PCINT и чтение из PINn

long pos = 0;
int8_t  lastState = 0;
const int8_t increment[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
void setup() {
 
  Serial.begin(9600);
   DDRC &=~((1<<PC2)|(1<<PC3));
   PORTC |= (1 << PC2)| (1 << PC3) ;

  PCICR |= (1 << PCIE1);   
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  
}

ISR(PCINT1_vect) {
 // uint8_t state = ~PINC&(1<<PC2) | (~PINC&(1<<PC3) << 1);
 uint8_t  state = digitalRead(A2) | (digitalRead(A3) << 1);
  if (state != lastState) {
    pos += increment[state | (lastState << 2)];
    lastState = state;
     pos=constrain(pos,0,255);
    Serial.println(pos);
    }
}

void loop() { 
}

*...меняю "digitalRead" на "~PINC&(1<<PCx)" , код перестает корректно работать... энкодер перестает корректно работать.

0
ph0en1x #30ph0en1x
13 Декабрь 2020 03:48

Здравствуйте. Не понятно зачем вы используете операции инверсии (логического НЕ, ~), ведь перед DigitalRead нет "!".

Попробуйте вот такие записи:

uint8_t state = (PINС & (1 << PC2)) | ((PINС & (1 << PC3)) << 1);
uint8_t state = (PINС & (1 << 2)) | ((PINС & (1 << 3)) << 1);

В данной статье описано как работают подобные конструкции, с примерами.

0