¿Por qué el código AVR utiliza desplazamiento de bits [cerrado]

7

En la programación AVR, los bits de registro se establecen invariablemente desplazando 1a la izquierda a la posición de bit adecuada, y se borran con el complemento de uno de los mismos.

Ejemplo: para un ATtiny85, podría configurar PORTB, b 4 así:

PORTB |= (1<<PB4);

o despejarlo así:

PORTB &= ~(1<<PB4);

Mi pregunta es: ¿por qué se hace de esta manera? El código más simple termina siendo un desastre de cambios de bits. ¿Por qué los bits se definen como posiciones de bits en lugar de máscaras?

Por ejemplo, el encabezado IO para el ATtiny85 incluye esto:

#define PORTB   _SFR_IO8(0x18)
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

Para mí, sería mucho más lógico definir los bits como máscaras (así):

#define PORTB   _SFR_IO8(0x18)
#define PB5     0x20
#define PB4     0x10
#define PB3     0x08
#define PB2     0x04
#define PB1     0x02
#define PB0     0x01

Entonces podríamos hacer algo como esto:

// as bitmasks
PORTB |=  PB5 |  PB3 |  PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;

para activar y desactivar los bits b 5 , b 3 y b 0 , respectivamente. Opuesto a:

// as bit-fields
PORTB |=  (1<<PB5) |  (1<<PB3) |  (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);

El código de máscara de bits lee mucho más claramente: bits fijados PB5, PB3y PB0. Además, parecería guardar operaciones ya que los bits ya no necesitan ser desplazados.

Pensé que tal vez se hizo de esta manera para preservar la generalidad con el fin de permitir el código de portabilidad de un AVR de n bits a un m- bit (por ejemplo, 8 bits a 32 bits). Pero este no parece ser el caso, ya que #include <avr/io.h>resuelve los archivos de definición específicos del microcontrolador de destino. Incluso el cambio de objetivos de un ATtiny de 8 bits a un Atmega de 8 bits (donde las definiciones de bits cambian sintácticamente de PBxa PORTBx, por ejemplo), requiere cambios de código.

Blair Fonville
fuente
3
Secundo esto. Incluso utilizar lo omnipresente en _BV(b)lugar de (1<<b)hacer las cosas innecesariamente desordenadas. Normalmente defino la mnemotecnia de bits con _BV(), por ejemplo #define ACK _BV(1).
calcio3000
2
Una vez que se da cuenta de que el compilador los interpretará como la misma constante, que usar en el código fuente es realmente una cuestión de preferencia. En su propio código, haga lo que considere más sabio; Al modificar los proyectos existentes, manténgase en sus tradiciones.
Chris Stratton
3
"Dado que un enfoque de máscara de bits produciría claramente un código de usuario final más legible", su opinión personal. Me resulta mucho más claro cambiar 1 y 0 al lugar correcto que tener que adivinar si varios números que se suman son máscaras de bits o no.
Tom Carpenter
3
@TomCarpenter Interesante. Bueno, quizás sin querer hice una pregunta basada en una opinión. De cualquier manera, ha habido algunos buenos comentarios. Viniendo de más de un fondo DSP (TI) (donde la máscara de bits es la norma), parecía una sintaxis tan extraña que supuse que había alguna razón concreta para ello.
Blair Fonville
1
@BlairFonville Tal vez ya lo sepa, pero los ARM funcionan exactamente como usted describe (con máscaras de bits).
Chi

Respuestas:

7

El código más simple termina siendo un desastre de cambios de bits. ¿Por qué los bits se definen como posiciones de bits en lugar de máscaras?

No, en absoluto. Los cambios están solo en el código fuente C, no en el código máquina compilado. Todos los ejemplos que mostraste pueden y serán resueltos por el compilador en tiempo de compilación porque son expresiones constantes simples.

(1<<PB4) es solo una forma de decir "bit PB4".

  • Por lo tanto, no solo funciona, no crea más tamaño de código.
  • También tiene sentido que el programador humano nombre los bits por su índice (por ejemplo, 5) y no por su máscara de bits (por ejemplo, 32) porque de esta manera se pueden usar números consecutivos 0..7 para identificar los bits en lugar de la potencia incómoda de dos (1, 2, 4, 8, .. 128).

  • Y hay otra razón (quizás la razón principal):
    los archivos de encabezado C no solo se pueden usar para el código C sino también para el código fuente del ensamblador (o el código del ensamblador incluido en el código fuente C). En el código del ensamblador AVR, definitivamente no solo desea usar máscaras de bits (que se pueden crear a partir de índices mediante desplazamiento de bits). Para algunas instrucciones de ensamblador de manipulación de bits AVR (por ejemplo, SBI, CBI, BST, BLD), debe usar índices de bits como operador inmediato en su código de operación de instrucciones.
    Solo si identifica fragmentos de SFR por índices(no por máscara de bits) puede usar dichos identificadores directamente como las instrucciones del ensamblador operando inmediato. De lo contrario, tenía que tener dos definiciones para cada bit SFR: una que define su índice de bits (que puede usarse, por ejemplo, como operando en las instrucciones de ensamblador de manipulación de bits mencionadas anteriormente) y una que define su máscara de bits (que solo puede usarse para instrucciones donde el byte completo es manipulado)

Cuajada
fuente
1
Entiendo que. No estoy cuestionando si funciona o no. Se que lo hace. Me pregunto por qué las definiciones están escritas tal como están. Para mí, mejoraría enormemente la legibilidad del código si se definieran como máscaras en lugar de posiciones de bits.
Blair Fonville
55
Creo que esta respuesta pierde el punto. Nunca habla sobre la eficiencia del código o el compilador. Se trata del desorden del código fuente .
tubería
44
@Blair Fonville: no hay una manera fácil de definir tal macro. Necesitaba calcular el logaritmo a la base 2. No existe una funcionalidad de preprocesador que calcule el logaritmo. Es decir, solo se podría hacer usando una tabla y eso, creo, sería una muy mala idea.
Cuajada
2
@pipe: No hablo de eso porque simplemente no lo considero como "contaminación del código" o "desorden del código fuente" (o como quieran llamarlo). Por el contrario, creo que incluso es útil recordarle al programador / lector que la constante que está usando es una potencia de dos (y eso se hace usando la expresión shift).
Cuajada
1
@RJR, Blair Fonville: por supuesto, es fácilmente posible definir tales macros PERO mientras que el uso de definiciones simples de preprocesador está bien, evitaría las macros de preprocesador (también conocidas como funciones de preprocesador) siempre que sea posible porque pueden realizar la depuración (recorriendo el código fuente C con el depurador) extremadamente intransparente.
Cuajada
4

Quizás el desplazamiento de bits no es el único caso de uso para las PB*definiciones. Quizás haya otros casos de uso en los que las PB*definiciones se usen directamente en lugar de cantidades de turno. Si es así, creo que el principio DRY lo llevaría a implementar un conjunto de definiciones que se pueden usar para ambos casos de uso (como estas PB*definiciones) en lugar de dos conjuntos diferentes de definiciones que tienen información repetitiva.

Por ejemplo, escribí una aplicación que puede tomar medidas de hasta 8 canales ADC. Tiene una interfaz para iniciar una nueva medición en la que puede especificar múltiples canales a través de un campo de 8 bits, un bit para cada canal. (Las mediciones se realizan en paralelo cuando se especifican múltiples canales). Luego tiene otra interfaz que devuelve los resultados de la medición para un canal individual. Entonces, una interfaz usa el número de canal como un cambio en un campo de bits y la otra interfaz usa el número de canal directamente. Definí una enumeración única para cubrir ambos casos de uso.

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
fuente