5 de novembro de 2010

Trabalhando com campos de bits

Nos primórdios da computação não havia como fugir da obrigação de se trabalhar diretamente com bits e bytes. Naquele tempo, um especialista tinha apenas o assembly para instruir o computador. Hoje, as linguagens de programação são de alto nível, liberando o programador para se ocupar com as regras de negócio de um sistema ao invés de mergulhar nos detalhes de como a máquina funciona.

Mas, como os computadores continuam funcionando do mesmo jeito, há momentos em que a manipulação direta de bits volta pra nos assombrar. Uma situação típica em que saber trabalhar com bits é imprescindível aparece quando precisamos que nosso programa se comunique com outros tipos de hardware. Isto aconteceu aqui na ABC71 em outubro, quando um de nossos Clientes precisou de uma integração de nosso ERP com balanças instaladas em seu chão de fábrica. A balança implementa um protocolo específico, transmitindo através da porta serial os bytes que representam seu estado atual e o peso que está sobre ela. A imagem abaixo ilustra o significado de cada bit presente em um dos bytes, trafegando com parte do status:
Status 1

Em C++, extrair esses bits isoladamente é muito simples já que a linguagem comporta o conceito de "mapa de bits". Trata-se de uma estrutura onde conseguimos especificar os limites de cada campo, informando quantos bits cada um deles ocupará na estrutura. O quadro a seguir mostra a declaração de um mapa de bits que corresponde ao byte de status exibido na imagem anterior.
typedef struct
{
UINT flagDecimal : 3; /* Ocupa 3 bits */
UINT pesoNegativo : 1; /* Ocupa 1 bit */
UINT pesoEmMovto : 1;
UINT saturacao : 1;
UINT sobrecarga : 1;
UINT bit7 : 1;
} TWStatus1;
Repare que a ordem de inclusão dos campos na estrutura respeita a ordem que os bits ocupam no byte em questão. Isto é, o campo para o bit de número 1 é incluído primeiro, enquanto o oitavo bit é o último campo da estrutura. O tipo de dado selecionado para cada campo é importante por uma questão semântica, sendo que a escolha deve levar em conta o uso que faremos deles. À exceção do indicador de casas decimais, os outros campos do exemplo poderiam ser do tipo boolean sem problemas.

E onde isso se encaixa na solução do problema de comunicação direta com os bytes recebidos da porta serial ? Ao receber da porta serial um buffer (sequência de bytes) com os dados esperados, podemos fazer com que uma variável com o tipo da estrutura que definimos aponte o mesmo endereço de memória do buffer, compartilhando com ele o conteúdo. A diferença é que nossa estrutura tem os campos bem definidos e assim podemos usá-los diretamente em nosso programa, sem termos que nos preocupar com o significado dos bits contidos no buffer. Fica mais fácil compreender vendo o corpo da função que trata os bytes recebidos da serial :
void __fastcall TWSerialComm::DoOnReceiveData(TObject* Sender, void* DataPtr, unsigned int DataSize)
{
TWStatus1 *lStatus = (TWStatus1 *)DataPtr;
if (lStatus->pesoEmMovto == 0)
{
/* ... */
}
}

Embora não seja o caso no exemplo apresentado, há um outro detalhe ao qual devemos prestar atenção: o alinhamento de bytes. Em estruturas mais complexas, o alinhamento dos campos na memória, determinado pelo compilador, pode fazer com que a estrutura ocupe mais bytes do que o buffer recebido. Como resultado, as informações contidas no buffer deixam de se encaixar perfeitamente na estrutura, que passa a conter apenas sujeira. Por isso, é importante forçar o alinhamento correto da estrutura, evitando a existência dos espaços extras no interior dela. Em C++ Builder, há uma diretiva de compilação para controlar o alinhamento no nível de estrutura, chamada pragma pack:
/* Força em 1 o alinhamento da estrutura */
#pragma pack(push)
#pragma pack(1)
typedef struct
{
char STX;
double peso;
double tara;
TWStatus1 status;
char ETX;
} TWDadosBalanca;
#pragma pack(pop)

Em Delphi, manipular bits é um pouco mais complexo devido à falta do conceito de campo de bits na sintaxe da linguagem. Neste caso, será preciso apelar para a aritmética binária, operando com máscaras de bits e operadores lógicos. Há uma solução bastante elegante no site Delphi's Corner onde é construída uma classe com propriedades indexadas para encapsular o tratamento de mais baixo nível envolvido na aritmética binária.

2 comentários :

Anônimo disse...

Muito bom o post!
Esse mundo de Bytes e bits pelo manos para min,
é um novo mundo a desbravar...hehe
meus parabéns...

Fabiano disse...

Oh valeu tava quebrando a cabeça com isto e este esta diretiva resolveu, vlw

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.