В программировании AVR биты регистров всегда устанавливаются смещением влево 1
в соответствующую позицию бита, и они очищаются с помощью одного и того же дополнения.
Пример: для ATtiny85 я мог бы установить PORTB, b 4 следующим образом:
PORTB |= (1<<PB4);
или очистить это так:
PORTB &= ~(1<<PB4);
У меня вопрос: почему это так? Самый простой код заканчивает тем, что был беспорядочным сдвигом. Почему биты определяются как битовые позиции вместо масок.
Например, заголовок ввода-вывода для ATtiny85 включает в себя следующее:
#define PORTB _SFR_IO8(0x18)
#define PB5 5
#define PB4 4
#define PB3 3
#define PB2 2
#define PB1 1
#define PB0 0
Для меня было бы намного логичнее вместо этого определять биты как маски (как это):
#define PORTB _SFR_IO8(0x18)
#define PB5 0x20
#define PB4 0x10
#define PB3 0x08
#define PB2 0x04
#define PB1 0x02
#define PB0 0x01
Таким образом, мы могли бы сделать что-то вроде этого:
// as bitmasks
PORTB |= PB5 | PB3 | PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;
включить или выключить биты b 5 , b 3 и b 0 соответственно. В отличие от:
// as bit-fields
PORTB |= (1<<PB5) | (1<<PB3) | (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);
Код битовый читает гораздо более четко: набор бит PB5
, PB3
и PB0
. Кроме того, казалось бы, чтобы сохранить операции, так как биты больше не должны быть сдвинуты.
Я подумал, может быть, это было сделано таким образом, чтобы сохранить общность, чтобы позволить переносить код с n -битного AVR на m -бит (например, 8-битный или 32-битный). Но, похоже, это не так, поскольку он #include <avr/io.h>
разрешает файлы определения, специфичные для целевого микроконтроллера. Даже изменяя цели из 8-битного Attiny к 8-битовой Atmega (где изменить битое определение синтаксический от PBx
до PORTBx
, к примеру), требует изменений коды.
источник
_BV(b)
вместо того, чтобы(1<<b)
излишне запутывать вещи. Я обычно определяю битовую мнемонику с помощью_BV()
, например#define ACK _BV(1)
.Ответы:
Нет, совсем нет. Изменения только в исходном коде C, а не в скомпилированном машинном коде. Все примеры, которые вы показали, могут и будут решаться компилятором во время компиляции, потому что они являются простыми константными выражениями.
(1<<PB4)
это просто способ сказать "бит PB4".Для программиста-человека также имеет смысл называть биты по их индексу (например, 5), а не по их битовой маске (например, 32), потому что таким образом последовательные числа 0..7 могут использоваться для идентификации битов вместо неуклюжей степени два (1, 2, 4, 8, .. 128).
И есть еще одна причина (может быть, основная причина):
файлы заголовков C могут использоваться не только для C-кода, но и для исходного кода на ассемблере (или кода на ассемблере, встроенного в исходный код на C). В коде ассемблера AVR вы определенно не только хотите использовать битовые маски (которые могут быть созданы из индексов путем сдвига битов). Для некоторых инструкций ассемблера битов AVR (например, SBI, CBI, BST, BLD) вы должны использовать битовые индексы в качестве непосредственного оператора в их коде команды.
Только если вы идентифицируете биты SFR по индексам(не по битовой маске) вы можете использовать такие идентификаторы непосредственно как непосредственный операнд инструкций ассемблера. В противном случае вам нужно было иметь два определения для каждого бита SFR: одно, определяющее его битовый индекс (который может использоваться, например, в качестве операнда в вышеупомянутых битах, манипулирующих инструкциями ассемблера), и одно определяющее его битовую маску (которая может использоваться только для инструкций, в которых весь байт манипулировать).
источник
Возможно, сдвиг битов - не единственный случай использования
PB*
определений. Возможно, есть и другие случаи использования, когдаPB*
определения используются непосредственно, а не в виде сумм за смену. Если это так, то я полагаю, что принцип СУХОГО привел бы вас к реализации одного набора определений, который можно использовать для обоих вариантов использования (например, этихPB*
определений), а не двух разных наборов определений, которые имеют повторяющуюся информацию.Например, я написал приложение, которое может измерять до 8 каналов АЦП. Он имеет один интерфейс для начала нового измерения, в котором вы можете указать несколько каналов через 8-битное поле, по одному биту для каждого канала. (Измерения выполняются параллельно, когда указано несколько каналов.) Затем он имеет другой интерфейс, который возвращает результаты измерений для отдельного канала. Таким образом, один интерфейс использует номер канала как сдвиг в битовое поле, а другой интерфейс использует номер канала напрямую. Я определил одно перечисление, чтобы охватить оба варианта использования.
источник