Почему код AVR использует сдвиг битов [закрыт]

7

В программировании 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, к примеру), требует изменений коды.

Блэр Фонвилль
источник
3
Я второй это. Даже использование вездесущего _BV(b)вместо того, чтобы (1<<b)излишне запутывать вещи. Я обычно определяю битовую мнемонику с помощью _BV(), например #define ACK _BV(1).
calc3000
2
Как только вы поймете, что компилятор будет интерпретировать их как одну и ту же константу, использование которой в исходном коде действительно является вопросом предпочтения. В своем собственном коде делай то, что считаешь мудрым; изменяя существующие проекты, придерживайтесь их традиций.
Крис Страттон
3
«Поскольку подход с битовой маской явно даст более читаемый код конечного пользователя» - ваше личное мнение. Я считаю, что гораздо проще сместить 1 и 0 в нужное место, чем угадывать, являются ли несколько чисел, добавляемых вместе, битовыми масками или нет.
Том Карпентер
3
@TomCarpenter Интересно. Ну, может быть, я случайно задал вопрос, основанный на мнении. В любом случае, были хорошие отзывы. Исходя из большей части (TI) DSP-фона (где битовая маска является нормой), это просто казалось таким странным синтаксисом, что я подумал, что для этого есть какая-то конкретная причина.
Блэр Фонвилль
1
@BlairFonville Может быть, вы уже знаете это, но ARM работает точно так же, как вы описали (с битовыми масками).
Чи

Ответы:

7

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

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

(1<<PB4) это просто способ сказать "бит PB4".

  • Так что это не только работает, но и не создает больше размера кода.
  • Для программиста-человека также имеет смысл называть биты по их индексу (например, 5), а не по их битовой маске (например, 32), потому что таким образом последовательные числа 0..7 могут использоваться для идентификации битов вместо неуклюжей степени два (1, 2, 4, 8, .. 128).

  • И есть еще одна причина (может быть, основная причина):
    файлы заголовков C могут использоваться не только для C-кода, но и для исходного кода на ассемблере (или кода на ассемблере, встроенного в исходный код на C). В коде ассемблера AVR вы определенно не только хотите использовать битовые маски (которые могут быть созданы из индексов путем сдвига битов). Для некоторых инструкций ассемблера битов AVR (например, SBI, CBI, BST, BLD) вы должны использовать битовые индексы в качестве непосредственного оператора в их коде команды.
    Только если вы идентифицируете биты SFR по индексам(не по битовой маске) ​​вы можете использовать такие идентификаторы непосредственно как непосредственный операнд инструкций ассемблера. В противном случае вам нужно было иметь два определения для каждого бита SFR: одно, определяющее его битовый индекс (который может использоваться, например, в качестве операнда в вышеупомянутых битах, манипулирующих инструкциями ассемблера), и одно определяющее его битовую маску (которая может использоваться только для инструкций, в которых весь байт манипулировать).

творог
источник
1
Я это понимаю. Я не спрашиваю, работает ли это или нет. Я знаю, что это так. Я спрашиваю, почему определения написаны так, как они есть. Для меня это значительно улучшило бы читаемость кода, если бы они определялись как маски, а не позиции битов.
Блэр Фонвилль
5
Я думаю, что этот ответ не имеет смысла. Он никогда не говорит об эффективности кода или компилятора. Все дело в беспорядке исходного кода .
труба
4
@Blair Fonville: нет простого способа определить такой макрос. Требовалось рассчитать логарифм по основанию 2. Функциональность препроцессора для вычисления логарифма отсутствует. Т.е. это можно сделать только с помощью таблицы, и это, я думаю, было бы очень плохой идеей.
Творог
2
@pipe: я не говорю об этом, потому что я просто не рассматриваю это как «загрязнение кода» или «беспорядок исходного кода» (или как вы хотите это назвать). Напротив, я думаю, что даже полезно напомнить программисту / читателю, что константа, которую он использует, является степенью двойки (и это делается с помощью выражения сдвига).
Творог
1
@RJR, Блэр Фонвилль: конечно, можно легко определить такие макросы, НО при использовании простых определений препроцессора - это нормально, я бы по возможности избегал макросов препроцессора (так называемые функции препроцессора), потому что они могут выполнять отладку (пошаговое выполнение исходного кода C с помощью отладчик) предельно прозрачный.
Творог
4

Возможно, сдвиг битов - не единственный случай использования PB*определений. Возможно, есть и другие случаи использования, когда PB*определения используются непосредственно, а не в виде сумм за смену. Если это так, то я полагаю, что принцип СУХОГО привел бы вас к реализации одного набора определений, который можно использовать для обоих вариантов использования (например, этих PB*определений), а не двух разных наборов определений, которые имеют повторяющуюся информацию.

Например, я написал приложение, которое может измерять до 8 каналов АЦП. Он имеет один интерфейс для начала нового измерения, в котором вы можете указать несколько каналов через 8-битное поле, по одному биту для каждого канала. (Измерения выполняются параллельно, когда указано несколько каналов.) Затем он имеет другой интерфейс, который возвращает результаты измерений для отдельного канала. Таким образом, один интерфейс использует номер канала как сдвиг в битовое поле, а другой интерфейс использует номер канала напрямую. Я определил одно перечисление, чтобы охватить оба варианта использования.

typedef enum
{
    CHANNEL_XL_X = 0,
    CHANNEL_XL_Y = 1,
    CHANNEL_XL_Z = 2,
    CHANNEL_G_X = 3,
    CHANNEL_G_Y = 4,
    CHANNEL_G_Z = 5,
    CHANNEL_AUX1 = 6,
    CHANNEL_AUX2 = 7
} ChannelNum;

struct MeasurementResult;

void StartMeasurement(uint8_t channel_mask);
MeasurementResult ReadMeasurementResult(ChannelNum channel_num);

main
{
    ...

    StartMeasurement( (1 << CHANNEL_XL_X) | (1 << CHANNEL_XL_Y) | (1 << CHANNEL_XL_Z) );

    meas_result_x = ReadMeasurementResult(CHANNEL_XL_X);
    meas_result_y = ReadMeasurementResult(CHANNEL_XL_Y);
    meas_result_z = ReadMeasurementResult(CHANNEL_XL_Z);
}
kkrambo
источник