En la programación AVR, los bits de registro se establecen invariablemente desplazando 1
a 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
, PB3
y 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 PBx
a PORTBx
, por ejemplo), requiere cambios de código.
fuente
_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)
.Respuestas:
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".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)
fuente
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 lasPB*
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 estasPB*
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.
fuente