Bits em Linguagem C - Conceito e Aplicação - Embarcados
Bits em Linguagem C – Conceito e Aplicação
O domínio de operações com bits é essencial quando se está trabalhando em programação de microcontroladores. Sempre será necessário usar bits para a configuração de registradores, configuração de funções específicas e também para leitura de status para tomada de decisão. Quando se trabalha com a linguagem Assembly essas operações são mais naturais, já que existem operações diretas para isso. Por outro lado a linguagem C também trás a facilidade de acesso aos registradores e manipulação de bits através dos operadores lógicos bit a bit. Neste artigo iremos abordar o uso de bits em Linguagem C e as principais operações lógicas em linguagem C.
Operações lógicas para usar bits em Linguagem C
Antes de conhecer os operadores binários da Linguagem C, vamos relembrar as operações lógicas em sistemas digitais. Thiago Lima apresentou um excelente resumo sobre portas lógicas e vamos usar o seu artigo como referência.
Lógica Não (NOT)
Inverte o estado do bit.
Lógica E (AND)
A saída será igual a 1 somente se todos os bits de entrada forem iguais a 1. Se pelo menos um dos bits for 0, a saída será 0.
Lógica OU (OR)
A saída assumirá nível lógico 1 se pelo menos 1 bit de entrada for igual a 1. Somente assumirá nível lógico 0 se todas as entradas apresentarem nível lógico 0.
Lógica Ou-Exclusivo (XOR)
A saída será igual a 0 se as entradas forem iguais. Caso as entradas forem diferentes a saída assumirá o valor 1.
Operadores binários em linguagem C
A linguagem C apresenta uma grande variedade de operadores. A seguir são apresentados os operadores para operações binarias, bit a bit:
NOT ou Complemento de 1
Os operadores &, |, ^ e ~ funcionam como apresentado anteriormente e têm as saidas conforme exibidos em suas tabelas verdades. Os operadores de deslocamento fazem o shift de bits para esquerda ou direita.
Vamos assumir que x incialmente tenha o valor 0b00000001, valor 1 em decimal.
x = (x<<2);
Após a operação acima, será atribuido a x o valor 0b000000100, valor 4 em decimal.
Se em seguida realizarmos uma operação de deslocamento à direita:
Após essa operação será atribuído à variável x o valor 0b00000010, valor 2 em decimal.
Note que a operação de deslocamento à esquerda multiplica por dois a cada shift. Já o deslocamento à direita, divide por dois a cada shift. Dessa forma pode-se também utilizar esses operadores para multiplicação ou divisão por 2, sendo mais eficiente que utilizando o operador de multiplicação.
Usando máscara de bits
Para manipulação dos bits utilizam-se máscaras de bits que possibilitam a alteração ou leitura de bits específicos sem a alteração de outros. A seguir são exibidas algumas operações.
Ativando um bit
Para ativar um bit especifico de uma variável ou registrador, ou seja, colocar nível lógico 1, utiliza-se o operador |(OU). Por exemplo, desejamos colocar em nivel 1 o bit 5 do PORTB, no ATmega328. Para isso fazemos a seguinte operação:
PORTB = PORTB|0b00100000;
-> valor presente no registrador PORTB
-> máscara para alteração do bit 5
-> resultado, apenas o bit 5 é alterado
Para facilidade na criação da máscara, utiliza-se o operador de deslocamento à esquerda, ficando da seguinte forma:
Será feita a lógica OU com o byte criado na operação (1<<5), que deslocou o valor '1' 5 vezes para a esquerda.
A operação pode ser simplificada, ficando da seguinte forma:
Limpando um bit
Para limpar um bit específico de uma variável ou registrador, ou seja, colocar nível lógico 0, utiliza-se o operador & (AND). Vamos utilizar o mesmo registrador e bit utilizado no exemplo anterior como exemplo:
PORTB = PORTB & 0b11011111;
Usando o operador de deslocamento ficará da seguinte forma:
PORTB = PORTB & (~(1<<5);
Note que é necessário inverter os valores para a máscara ficar da forma desejada.
Invertendo o estado de um bit
Para inverter o estado lógico de um bit utiliza-se o operador ^ (OU-Exclusivo). Continuamos com o mesmo bit e registrador dos exemplos anteriores:
PORTB = PORTB^0b00100000;
-> resultado, o bit 5 é invertido
PORTB = PORTB ^ (1<<5);
Testando valor de um bit
Para teste de um bit também utilizaremos a lógica & (AND) para operação com a máscara de bits. Por exemplo, temos uma tecla ligada ao pino 2 do PORTD do Atmega328. Para leitura desse pino utilizamos o registrador PIND. A operação ficará da seguinte forma:
PIND & (1<<2)
-> valor presente no registrador PIND
-> máscara para alteração do bit 2
-> resultado, o valor do bit estará presente no resultado
Caso o bit de teste tenha o valor 1, o resultado conterá o valor 1 somente na posição do bit, caso contrário o resultado será 0.
Esta operação pode ser usada em uma estrutura de descição if, por exemplo:
if (PIND & (1 << 2)) { //executa comandos dentro do if se o pino testado estiver com o valor lógico 1 }
//executa comandos dentro do if se o pino testado estiver com o valor lógico 1
Fernando Deluno Garcia apresentou uma excelente série sobre o Pré-Processador C. Utilizando seus atigos como referência, vamos criar algumas macros para manipulação de bits utilizando as operações apresentadas.
toogle bit
O exemplo a seguir exibe o uso das macros apresentadas para manipulação de bits no Atmega328P. Foi utilizada a IDE Arduino, para que você possa facilmente testar, utilizando a placa Arduino UNO.
#define setBit(valor,bit) (valor |= (1<<bit)) #define clearBit(valor,bit) (valor &= ~(1<<bit)) #define testBit(valor,bit) (valor & (1<<bit)) #define toggleBit(valor, bit) (valor ^= (1<<bit)) #define LED 5 //pino PB5 para o LED #define BT 2 //pino PD2 para o BT void setup() { setBit(DDRB,LED); //configura pino PB5 como saída clearBit(DDRD,BT); //configura pino PD2 como entrada } void loop() { if(!testBit(PIND,BT)){ //se botão pressionado setBit(PORTB,LED); //liga led } else{ //se não clearBit(PORTB,LED); //apaga led } }
#define toggleBit(valor, bit) (valor ^= (1<<bit))
#define LED 5 //pino PB5 para o LED
#define BT 2 //pino PD2 para o BT
setBit(DDRB,LED); //configura pino PB5 como saída
clearBit(DDRD,BT); //configura pino PD2 como entrada
if(!testBit(PIND,BT)){ //se botão pressionado
setBit(PORTB,LED); //liga led
clearBit(PORTB,LED); //apaga led
Podemos também usar a compilação condicional, conforme apresentada no artigo Pré-processador C: Compilação condicional, escrito por Fernando Deluno Garcia, e testar o tamanho de código gerado, usando macros e as funções do Arduino:
//#define ARDUINO_FUNC 1 //comente para uso de macros #define setBit(valor,bit) (valor |= (1<<bit)) #define clearBit(valor,bit) (valor &= ~(1<<bit)) #define testBit(valor,bit) (valor & (1<<bit)) #define toggleBit(valor, bit) (valor ^= (1<<bit)) #ifdef ARDUINO_FUNC #define LED 13 //pino 13 para o LED #define BT 2 //pino 2 para o BT #else #define LED 5 //pino PB5 para o LED #define BT 2 //pino PD2 para o BT #endif void setup() { #ifndef ARDUINO_FUNC setBit(DDRB,LED); //configura pino PB5 como saída clearBit(DDRD,BT); //configura pino PD2 como entrada #else pinMode(LED, OUTPUT); pinMode(BT, INPUT); #endif } void loop() { while(1){ toggleBit(PORTB,LED); delay(1000); } #ifndef ARDUINO_FUNC if(!testBit(PIND,BT)){ //se botão pressionado setBit(PORTB,LED); //liga led } else{ //se não clearBit(PORTB,LED); //apaga led } #else if(digitalRead(BT) == LOW){ digitalWrite(LED,HIGH); } else{ digitalWrite(LED,LOW); } #endif }
//#define ARDUINO_FUNC 1 //comente para uso de macros
#ifdef ARDUINO_FUNC
#define LED 13 //pino 13 para o LED
#define BT 2 //pino 2 para o BT
#ifndef ARDUINO_FUNC
toggleBit(PORTB,LED);
if(digitalRead(BT) == LOW){
Compile nas duas condições e verifique o tamanho do código gerado em cada uma delas. Note que o código gerado utilizando macros é bem menor do que quando utilizando as funções Arduino.
As macros utilizadas acimas podem ser aplicadas em diversas plataformas e facilitam em operações com bits.
AnteriorAnteriorUsando Visual Studio (VS) para desenvolver em C/C++ para Raspberry Pi
PróximoAtmel Studio 7 está disponívelPróximo
Ivanizio Ramalho
Olá Fábio, uma didática excelente, parabéns!
Reply to Ivanizio Ramalho
Muito obrigado Ivanizio. Muito bom ler isso!
Se tiver alguma sugestão para artigos, deixe comentário.
outro ponto interessante também é a utilização de bit-fields para a realização destas operações de manipulação de bits. É uma operação que, dependendo do compilador e da arquitetura, pode deixar a desejar se comparado a manipulação dos bits através de máscaras e operações de deslocamento.
Muito legal o seu artigo, Fabio. Parabéns!
ótima observação Rafael, realmente são muito utilizados.
Olá Humberto, muito obrigado!
Valeu Pedrão. Abraços
Ótimo artigo, Fábio. Apenas apontando uma correção: na parte onde você explica a leitura dos bits, fala-se no texto da leitura do 2º bit mas no código lê-se o 4º. No mais, parabéns!