Na programação do AVR, os bits de registro são invariavelmente configurados deslocando-se a esquerda 1
para 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
, PB3
e 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 PBx
para PORTBx
, por exemplo), requer alterações de código.
fonte
_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)
.Respostas:
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".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).
fonte
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 asPB*
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 estesPB*
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.
fonte