Por que o código AVR usa deslocamento de bits [fechado]

7

Na programação do AVR, os bits de registro são invariavelmente configurados deslocando-se a esquerda 1para a posição de bit apropriada - e são limpos pelo complemento do mesmo.

Exemplo: para um ATtiny85, eu poderia definir PORTB, b 4 como este:

PORTB |= (1<<PB4);

ou limpe assim:

PORTB &= ~(1<<PB4);

Minha pergunta é: Por que isso é feito dessa maneira? O código mais simples acaba sendo uma bagunça de mudanças de bits. Por que os bits são definidos como posições de bits em vez de máscaras.

Por exemplo, o cabeçalho de E / S para o ATtiny85 inclui isso:

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

Para mim, seria muito mais lógico definir os bits como máscaras (assim):

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

Então, poderíamos fazer algo assim:

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

para ativar e desativar os bits b 5 , b 3 e b 0 , respectivamente. Ao contrário de:

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

O código bitmask lê muito mais claramente: bits definidos PB5, PB3e PB0. Além disso, parece salvar operações, já que os bits não precisam mais ser trocados.

Pensei que talvez tivesse sido feito dessa maneira para preservar a generalidade, a fim de permitir a transferência de código de um AVR de n bits para um m -bit (exemplo de 8 bits para 32 bits). Mas isso não parece ser o caso, pois #include <avr/io.h>resolve os arquivos de definição específicos para o microcontrolador de destino. Mesmo alterar alvos de um ATtiny de 8 bits para um Atmega de 8 bits (onde as definições de bits mudam sintaticamente de PBxpara PORTBx, por exemplo), requer alterações de código.

Blair Fonville
fonte
3
Eu segundo isso. Mesmo utilizando o onipresente em _BV(b)vez de (1<<b)torna as coisas desnecessariamente confusas. Eu normalmente defino mnemônicos de bits com _BV(), por exemplo #define ACK _BV(1).
calcium3000
2
Uma vez que se perceba que o compilador irá interpretá-los como a mesma constante, o que usar no código fonte é realmente uma questão de preferência. No seu próprio código, faça o que achar mais sábio; na modificação de projetos existentes, mantenha suas tradições.
21418 Chris Stratton
3
"Como uma abordagem de máscara de bit renderia claramente um código de usuário final mais legível" - sua opinião pessoal. Acho muito mais claro mudar 1 e 0 para o lugar certo do que ter que adivinhar se vários números que estão sendo adicionados são máscaras de bit ou não.
Tom Carpenter
3
@TomCarpenter Interessante. Bem, talvez eu tenha inadvertidamente feito uma pergunta baseada em opinião. De qualquer forma, houve um bom feedback. Vindo de mais de um fundo de DSP (TI) (onde a máscara de bits é a norma), parecia uma sintaxe tão estranha que eu imaginei que havia alguma razão concreta para isso.
Blair Fonville
1
@BlairFonville Talvez você já saiba disso, mas os ARMs funcionam exatamente como você descreve (com máscaras de bit).
Chi

Respostas:

7

O código mais simples acaba sendo uma bagunça de mudanças de bits. Por que os bits são definidos como posições de bits em vez de máscaras.

Não. Não mesmo. Os turnos são apenas no código fonte C, não no código de máquina compilado. Todos os exemplos que você mostrou podem e serão resolvidos pelo compilador no momento da compilação, porque são simples expressões constantes.

(1<<PB4) é apenas uma maneira de dizer "bit PB4".

  • Portanto, não só funciona, como também não cria mais tamanho de código.
  • Também faz sentido para o programador humano nomear os bits por seu índice (por exemplo, 5) e não por sua máscara de bits (por exemplo, 32), porque desta forma os números consecutivos 0..7 podem ser usados ​​para identificar os bits, em vez da potência desajeitada de bits. dois (1, 2, 4, 8, .. 128).

  • E há outro motivo (talvez o principal): os
    arquivos do cabeçalho C não podem ser usados ​​apenas para o código C, mas também para o código-fonte do assembler (ou o código do assembler embutido no código-fonte C). No código do assembler do AVR, você definitivamente não quer apenas usar máscaras de bits (que podem ser criadas a partir de índices por deslocamento de bits). Para algumas instruções do assembler de manipulação de bits do AVR (por exemplo, SBI, CBI, BST, BLD), você deve usar índices de bits como operador imediato em seu código operacional de instruções.
    Somente se você identificar bits de SFRs por índices(não pela máscara de bit), você pode usar esses identificadores diretamente, como o operando imediato das instruções do montador. Caso contrário, você tinha que ter duas definições para cada bit SFR: uma definindo seu índice de bits (que pode ser usado, por exemplo, como operando nas instruções de montagem de manipulação de bits mencionadas acima) e uma definindo sua máscara de bits (que só pode ser usada para instruções em que todo o byte é manipulado).

Coalhada
fonte
1
Eu entendi aquilo. Não estou questionando se funciona ou não. Eu sei que sim. Estou perguntando por que as definições estão escritas como estão. Para mim, melhoraria bastante a legibilidade do código se elas fossem definidas como máscaras em vez de posições de bits.
Blair Fonville
5
Eu acho que essa resposta erra o ponto. Ele nunca fala sobre eficiência de código ou compilador. É tudo sobre confusão de código fonte .
pipe
4
@ Blair Fonville: não há uma maneira fácil de definir uma macro. É necessário calcular o logaritmo para a base 2. Não há funcionalidade de pré-processador que calcula o logaritmo. Ou seja, isso só poderia ser feito usando uma tabela e isso, eu acho, seria uma péssima idéia.
Coalhada
2
@ pipe: Eu não falo sobre isso, porque eu simplesmente não considero isso como "poluição de código" ou "confusão de código-fonte" (ou o que você quiser chamar). Pelo contrário, acho até útil lembrar ao programador / leitor que a constante que ele está usando é uma potência de dois (e isso é feito usando a expressão shift).
Requeijão
1
@RJR, Blair Fonville: é claro que é fácil definir essas macros, mas ao usar um pré-processador simples, tudo bem. Eu evitaria macros de pré-processador (também conhecidas como funções de pré-processador) sempre que possível, porque elas podem fazer depuração (percorrendo o código-fonte C com o depurador) extremamente intransparente.
Coalhada
4

Talvez a mudança de bits não seja o único caso de uso para as PB*definições. Talvez haja outros casos de uso em que as PB*definições sejam usadas diretamente e não como valores de turno. Nesse caso, acredito que o princípio DRY o levaria a implementar um conjunto de definições que pode ser usado para ambos os casos de uso (como estes PB*definem) em vez de dois conjuntos diferentes de definições que possuem informações repetitivas.

Por exemplo, escrevi um aplicativo que pode fazer medições de até 8 canais ADC. Ele possui uma interface para iniciar uma nova medição na qual você pode especificar vários canais através de um campo de 8 bits, um bit para cada canal. (As medições são realizadas em paralelo quando vários canais são especificados.) Em seguida, há outra interface que retorna os resultados da medição para um canal individual. Portanto, uma interface usa o número do canal como uma mudança para um campo de bits e a outra interface usa o número do canal diretamente. Eu defini uma única enumeração para cobrir os dois 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
fonte