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

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

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

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

Содержание:

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

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

Один байт состоит из 8-ми бит. Каждый бит может иметь только два значения: 1 или 0. Нумерацию битов в байте принято вести справа налево, начиная с нуля (0).

Первый бит (номер 0, справа) является младшим, а последний (номер 7, слева) - старшим.

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

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

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

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

Порты в AVR микроконтроллерах имеют свое имя в виде "Pxx", например "PD1", где "D" это название порта, а "1" - номер бита (канала) в этом порте. Номера каналов начинаются с нуля (0).

Ниже показан пример битовой структуры порта PORTD:

  Регистр порта PORTD, 1 байт
Номер бита в регистре 
7 6 5 4 3 2 1 0
Название канала
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)

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

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

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

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

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

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

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

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

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

Чтобы переключить значение бита с номером 5 на "1" остается выполнить битовую операцию ИЛИ над текущим числом в регистре и получившимся числом-маской. Эта операция затронет в регистре только те биты, которые в маске имеют значение "1", а все остальные останутся без изменений:

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

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

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

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

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

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

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

Допустим нам нужно установить биты с номерами 1 и 5 в регистре порта PORTD, что соответствуют каналам PD1 и PD5:

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

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

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

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

Для того чтобы сбросить (переключить на 0) 4-й бит в байте регистра порта PORTD (10011101) мы сделаем следующее:

  1. Подготовим маску, задав значения бит для которых будем производить манипуляции в байте порта;
  2. Инвертируем биты в маске с помощью операции "~" (логическое "НЕ", инверсия);
  3. Выполним битовую операцию "&" (логическое "И") над текущим значением регистра и полученной после инверсии маской.

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

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)

Записав число 10001101 в регистр PORTD мы тем самым изменим значение бита с номером 4 на 0 с сохранением положений всех остальных бит.

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

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

Для одновременного сброса нескольких битов регистра (установки их значений в 0) можно использовать следующие конструкции из операторов:

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

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

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

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

Получив в результате булево (bool) значение его можно использовать в языка Си для работы с циклами и условными операторами.

Например, нам нужно проверить содержит ли единицу (1) бит с номером 2 в байте регистра PORTD, тем самым мы проверим задан ли высокий уровень на соответствующем каналу PD2 порта PORTD пине МК.

Допустим что текущее значение регистра PORTD - 10010101 (десятичное 149).

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

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

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

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

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

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

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

Результат нашего выражения - число 4, которое является логической истиной (True), а это значит что бит с номером 2 в байте регистра 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) бита с номером 2 в байте регистра для порта PORTD (канал PD2):

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

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

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

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

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

Например нам нужно погасить светодиод, который подключен к каналу PD5 порта PORTD. Если светодиод светится - то это значит что в на канале PD5 присутствует высокий уровень, соответственно бит под номером 5 в регистре порта PORTD установлен в 1.

Допустим что содержимое регистра порта PORTD сейчас - 10111010 (десятичное число 186).

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

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

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

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

В результате, если записать данное число в регистр PORTD, бит под номером 5 (который раньше был 1) будет установлен в 0 (1001 1010).

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

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

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

Заключение

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

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

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

Если статья оказалась полезной - помочь проекту можно тут: 👍 ПОМОЩЬ, 🎁 DONATE
Комментарии к публикации (29):
Дмитрий #1Дмитрий
16 Февраль 2017 10:57

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

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

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

+1
ph0en1x #3ph0en1x
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
Таким образом, мы выполнили установку 7-го (старшего) бита в регистре 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 #4VB99
04 Март 2017 09:40

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

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

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

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

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

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

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

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

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

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

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

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

В моем примере при помощи конструкции "1<<5" мы получаем байт с единственным установленным в "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 #10d9v4
24 Январь 2018 22:02

000'1' 0000 (16)

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

00'1'0 0000 (16)

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

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

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

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

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

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

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

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

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

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

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

0
ph0en1x #16ph0en1x
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 #17Flagmans
20 Декабрь 2018 19:22

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

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

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

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

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

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

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

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

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

+3
Santehnik #22Santehnik
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 #23ph0en1x
18 Март 2019 12:45

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

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

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

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

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

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

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

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

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

+1
ph0en1x #26ph0en1x
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
Миша #27Миша
25 Июль 2020 00:43

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

Yahoo

+1
felix #28felix
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 #29ph0en1x
13 Декабрь 2020 03:48

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

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

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

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

0